From c67ac45617e016d04b05ca5bd59763fe3b236d34 Mon Sep 17 00:00:00 2001 From: awe Date: Tue, 18 Nov 2025 12:26:48 +0300 Subject: [PATCH] add stream_module --- README.md | 211 + SkyWatcher | 1 + beacon_track | 1 + build_and_run.sh | 56 + web_viewer/.venv/bin/Activate.ps1 | 247 + web_viewer/.venv/bin/activate | 70 + web_viewer/.venv/bin/activate.csh | 27 + web_viewer/.venv/bin/activate.fish | 69 + web_viewer/.venv/bin/flask | 8 + web_viewer/.venv/bin/pip | 8 + web_viewer/.venv/bin/pip3 | 8 + web_viewer/.venv/bin/pip3.12 | 8 + web_viewer/.venv/bin/python | 1 + web_viewer/.venv/bin/python3 | 1 + web_viewer/.venv/bin/python3.12 | 1 + .../blinker-1.9.0.dist-info/INSTALLER | 1 + .../blinker-1.9.0.dist-info/LICENSE.txt | 20 + .../blinker-1.9.0.dist-info/METADATA | 60 + .../blinker-1.9.0.dist-info/RECORD | 12 + .../blinker-1.9.0.dist-info/WHEEL | 4 + .../site-packages/blinker/__init__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 509 bytes .../__pycache__/_utilities.cpython-312.pyc | Bin 0 -> 2736 bytes .../blinker/__pycache__/base.cpython-312.pyc | Bin 0 -> 22013 bytes .../site-packages/blinker/_utilities.py | 64 + .../python3.12/site-packages/blinker/base.py | 512 + .../python3.12/site-packages/blinker/py.typed | 0 .../click-8.3.0.dist-info/INSTALLER | 1 + .../click-8.3.0.dist-info/METADATA | 84 + .../click-8.3.0.dist-info/RECORD | 40 + .../site-packages/click-8.3.0.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 28 + .../site-packages/click/__init__.py | 123 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4081 bytes .../click/__pycache__/_compat.cpython-312.pyc | Bin 0 -> 24203 bytes .../__pycache__/_termui_impl.cpython-312.pyc | Bin 0 -> 31620 bytes .../__pycache__/_textwrap.cpython-312.pyc | Bin 0 -> 2434 bytes .../click/__pycache__/_utils.cpython-312.pyc | Bin 0 -> 1209 bytes .../__pycache__/_winconsole.cpython-312.pyc | Bin 0 -> 11779 bytes .../click/__pycache__/core.cpython-312.pyc | Bin 0 -> 133440 bytes .../__pycache__/decorators.cpython-312.pyc | Bin 0 -> 22146 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 14785 bytes .../__pycache__/formatting.cpython-312.pyc | Bin 0 -> 13681 bytes .../click/__pycache__/globals.cpython-312.pyc | Bin 0 -> 2974 bytes .../click/__pycache__/parser.cpython-312.pyc | Bin 0 -> 20451 bytes .../shell_completion.cpython-312.pyc | Bin 0 -> 23346 bytes .../click/__pycache__/termui.cpython-312.pyc | Bin 0 -> 34532 bytes .../click/__pycache__/testing.cpython-312.pyc | Bin 0 -> 27418 bytes .../click/__pycache__/types.cpython-312.pyc | Bin 0 -> 50047 bytes .../click/__pycache__/utils.cpython-312.pyc | Bin 0 -> 24888 bytes .../python3.12/site-packages/click/_compat.py | 622 ++ .../site-packages/click/_termui_impl.py | 847 ++ .../site-packages/click/_textwrap.py | 51 + .../python3.12/site-packages/click/_utils.py | 36 + .../site-packages/click/_winconsole.py | 296 + .../python3.12/site-packages/click/core.py | 3347 +++++++ .../site-packages/click/decorators.py | 551 ++ .../site-packages/click/exceptions.py | 308 + .../site-packages/click/formatting.py | 301 + .../python3.12/site-packages/click/globals.py | 67 + .../python3.12/site-packages/click/parser.py | 532 + .../python3.12/site-packages/click/py.typed | 0 .../site-packages/click/shell_completion.py | 667 ++ .../python3.12/site-packages/click/termui.py | 877 ++ .../python3.12/site-packages/click/testing.py | 577 ++ .../python3.12/site-packages/click/types.py | 1209 +++ .../python3.12/site-packages/click/utils.py | 627 ++ .../flask-3.1.2.dist-info/INSTALLER | 1 + .../flask-3.1.2.dist-info/METADATA | 91 + .../flask-3.1.2.dist-info/RECORD | 58 + .../flask-3.1.2.dist-info/REQUESTED | 0 .../site-packages/flask-3.1.2.dist-info/WHEEL | 4 + .../flask-3.1.2.dist-info/entry_points.txt | 3 + .../licenses/LICENSE.txt | 28 + .../site-packages/flask/__init__.py | 61 + .../site-packages/flask/__main__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2541 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 249 bytes .../flask/__pycache__/app.cpython-312.pyc | Bin 0 -> 62496 bytes .../__pycache__/blueprints.cpython-312.pyc | Bin 0 -> 5008 bytes .../flask/__pycache__/cli.cpython-312.pyc | Bin 0 -> 43552 bytes .../flask/__pycache__/config.cpython-312.pyc | Bin 0 -> 16260 bytes .../flask/__pycache__/ctx.cpython-312.pyc | Bin 0 -> 19846 bytes .../__pycache__/debughelpers.cpython-312.pyc | Bin 0 -> 9158 bytes .../flask/__pycache__/globals.cpython-312.pyc | Bin 0 -> 1873 bytes .../flask/__pycache__/helpers.cpython-312.pyc | Bin 0 -> 25574 bytes .../flask/__pycache__/logging.cpython-312.pyc | Bin 0 -> 3275 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 17190 bytes .../flask/__pycache__/signals.cpython-312.pyc | Bin 0 -> 1230 bytes .../__pycache__/templating.cpython-312.pyc | Bin 0 -> 9939 bytes .../flask/__pycache__/testing.cpython-312.pyc | Bin 0 -> 13620 bytes .../flask/__pycache__/typing.cpython-312.pyc | Bin 0 -> 4138 bytes .../flask/__pycache__/views.cpython-312.pyc | Bin 0 -> 7029 bytes .../__pycache__/wrappers.cpython-312.pyc | Bin 0 -> 10061 bytes .../lib/python3.12/site-packages/flask/app.py | 1536 +++ .../site-packages/flask/blueprints.py | 128 + .../lib/python3.12/site-packages/flask/cli.py | 1135 +++ .../python3.12/site-packages/flask/config.py | 367 + .../lib/python3.12/site-packages/flask/ctx.py | 449 + .../site-packages/flask/debughelpers.py | 178 + .../python3.12/site-packages/flask/globals.py | 51 + .../python3.12/site-packages/flask/helpers.py | 641 ++ .../site-packages/flask/json/__init__.py | 170 + .../json/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6701 bytes .../json/__pycache__/provider.cpython-312.pyc | Bin 0 -> 9268 bytes .../json/__pycache__/tag.cpython-312.pyc | Bin 0 -> 13963 bytes .../site-packages/flask/json/provider.py | 215 + .../site-packages/flask/json/tag.py | 327 + .../python3.12/site-packages/flask/logging.py | 79 + .../python3.12/site-packages/flask/py.typed | 0 .../site-packages/flask/sansio/README.md | 6 + .../sansio/__pycache__/app.cpython-312.pyc | Bin 0 -> 33720 bytes .../__pycache__/blueprints.cpython-312.pyc | Bin 0 -> 31221 bytes .../__pycache__/scaffold.cpython-312.pyc | Bin 0 -> 30234 bytes .../site-packages/flask/sansio/app.py | 964 ++ .../site-packages/flask/sansio/blueprints.py | 632 ++ .../site-packages/flask/sansio/scaffold.py | 792 ++ .../site-packages/flask/sessions.py | 399 + .../python3.12/site-packages/flask/signals.py | 17 + .../site-packages/flask/templating.py | 219 + .../python3.12/site-packages/flask/testing.py | 298 + .../python3.12/site-packages/flask/typing.py | 93 + .../python3.12/site-packages/flask/views.py | 191 + .../site-packages/flask/wrappers.py | 257 + .../itsdangerous-2.2.0.dist-info/INSTALLER | 1 + .../itsdangerous-2.2.0.dist-info/LICENSE.txt | 28 + .../itsdangerous-2.2.0.dist-info/METADATA | 60 + .../itsdangerous-2.2.0.dist-info/RECORD | 22 + .../itsdangerous-2.2.0.dist-info/WHEEL | 4 + .../site-packages/itsdangerous/__init__.py | 38 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1640 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 1194 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2694 bytes .../__pycache__/exc.cpython-312.pyc | Bin 0 -> 3954 bytes .../__pycache__/serializer.cpython-312.pyc | Bin 0 -> 15435 bytes .../__pycache__/signer.cpython-312.pyc | Bin 0 -> 11300 bytes .../__pycache__/timed.cpython-312.pyc | Bin 0 -> 8748 bytes .../__pycache__/url_safe.cpython-312.pyc | Bin 0 -> 3544 bytes .../site-packages/itsdangerous/_json.py | 18 + .../site-packages/itsdangerous/encoding.py | 54 + .../site-packages/itsdangerous/exc.py | 106 + .../site-packages/itsdangerous/py.typed | 0 .../site-packages/itsdangerous/serializer.py | 406 + .../site-packages/itsdangerous/signer.py | 266 + .../site-packages/itsdangerous/timed.py | 228 + .../site-packages/itsdangerous/url_safe.py | 83 + .../jinja2-3.1.6.dist-info/INSTALLER | 1 + .../jinja2-3.1.6.dist-info/METADATA | 84 + .../jinja2-3.1.6.dist-info/RECORD | 57 + .../jinja2-3.1.6.dist-info/WHEEL | 4 + .../jinja2-3.1.6.dist-info/entry_points.txt | 3 + .../licenses/LICENSE.txt | 28 + .../site-packages/jinja2/__init__.py | 38 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1665 bytes .../__pycache__/_identifier.cpython-312.pyc | Bin 0 -> 2146 bytes .../__pycache__/async_utils.cpython-312.pyc | Bin 0 -> 4986 bytes .../__pycache__/bccache.cpython-312.pyc | Bin 0 -> 19357 bytes .../__pycache__/compiler.cpython-312.pyc | Bin 0 -> 104072 bytes .../__pycache__/constants.cpython-312.pyc | Bin 0 -> 1568 bytes .../jinja2/__pycache__/debug.cpython-312.pyc | Bin 0 -> 6593 bytes .../__pycache__/defaults.cpython-312.pyc | Bin 0 -> 1618 bytes .../__pycache__/environment.cpython-312.pyc | Bin 0 -> 76694 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7732 bytes .../jinja2/__pycache__/ext.cpython-312.pyc | Bin 0 -> 41925 bytes .../__pycache__/filters.cpython-312.pyc | Bin 0 -> 72342 bytes .../__pycache__/idtracking.cpython-312.pyc | Bin 0 -> 19207 bytes .../jinja2/__pycache__/lexer.cpython-312.pyc | Bin 0 -> 32088 bytes .../__pycache__/loaders.cpython-312.pyc | Bin 0 -> 32367 bytes .../jinja2/__pycache__/meta.cpython-312.pyc | Bin 0 -> 5502 bytes .../__pycache__/nativetypes.cpython-312.pyc | Bin 0 -> 7023 bytes .../jinja2/__pycache__/nodes.cpython-312.pyc | Bin 0 -> 58285 bytes .../__pycache__/optimizer.cpython-312.pyc | Bin 0 -> 2701 bytes .../jinja2/__pycache__/parser.cpython-312.pyc | Bin 0 -> 61215 bytes .../__pycache__/runtime.cpython-312.pyc | Bin 0 -> 48903 bytes .../__pycache__/sandbox.cpython-312.pyc | Bin 0 -> 18119 bytes .../jinja2/__pycache__/tests.cpython-312.pyc | Bin 0 -> 9062 bytes .../jinja2/__pycache__/utils.cpython-312.pyc | Bin 0 -> 34875 bytes .../__pycache__/visitor.cpython-312.pyc | Bin 0 -> 5377 bytes .../site-packages/jinja2/_identifier.py | 6 + .../site-packages/jinja2/async_utils.py | 99 + .../site-packages/jinja2/bccache.py | 408 + .../site-packages/jinja2/compiler.py | 1998 ++++ .../site-packages/jinja2/constants.py | 20 + .../python3.12/site-packages/jinja2/debug.py | 191 + .../site-packages/jinja2/defaults.py | 48 + .../site-packages/jinja2/environment.py | 1672 ++++ .../site-packages/jinja2/exceptions.py | 166 + .../python3.12/site-packages/jinja2/ext.py | 870 ++ .../site-packages/jinja2/filters.py | 1873 ++++ .../site-packages/jinja2/idtracking.py | 318 + .../python3.12/site-packages/jinja2/lexer.py | 868 ++ .../site-packages/jinja2/loaders.py | 693 ++ .../python3.12/site-packages/jinja2/meta.py | 112 + .../site-packages/jinja2/nativetypes.py | 130 + .../python3.12/site-packages/jinja2/nodes.py | 1206 +++ .../site-packages/jinja2/optimizer.py | 48 + .../python3.12/site-packages/jinja2/parser.py | 1049 ++ .../python3.12/site-packages/jinja2/py.typed | 0 .../site-packages/jinja2/runtime.py | 1062 ++ .../site-packages/jinja2/sandbox.py | 436 + .../python3.12/site-packages/jinja2/tests.py | 256 + .../python3.12/site-packages/jinja2/utils.py | 766 ++ .../site-packages/jinja2/visitor.py | 92 + .../markupsafe-3.0.3.dist-info/INSTALLER | 1 + .../markupsafe-3.0.3.dist-info/METADATA | 74 + .../markupsafe-3.0.3.dist-info/RECORD | 14 + .../markupsafe-3.0.3.dist-info/WHEEL | 7 + .../licenses/LICENSE.txt | 28 + .../markupsafe-3.0.3.dist-info/top_level.txt | 1 + .../site-packages/markupsafe/__init__.py | 396 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 20975 bytes .../__pycache__/_native.cpython-312.pyc | Bin 0 -> 627 bytes .../site-packages/markupsafe/_native.py | 8 + .../site-packages/markupsafe/_speedups.c | 200 + .../_speedups.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 44072 bytes .../site-packages/markupsafe/_speedups.pyi | 1 + .../site-packages/markupsafe/py.typed | 0 .../pip-24.0.dist-info/AUTHORS.txt | 760 ++ .../pip-24.0.dist-info/INSTALLER | 1 + .../pip-24.0.dist-info/LICENSE.txt | 20 + .../site-packages/pip-24.0.dist-info/METADATA | 88 + .../site-packages/pip-24.0.dist-info/RECORD | 1005 ++ .../pip-24.0.dist-info/REQUESTED | 0 .../site-packages/pip-24.0.dist-info/WHEEL | 5 + .../pip-24.0.dist-info/entry_points.txt | 4 + .../pip-24.0.dist-info/top_level.txt | 1 + .../python3.12/site-packages/pip/__init__.py | 13 + .../python3.12/site-packages/pip/__main__.py | 24 + .../site-packages/pip/__pip-runner__.py | 50 + .../pip/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 702 bytes .../pip/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 858 bytes .../__pip-runner__.cpython-312.pyc | Bin 0 -> 2221 bytes .../site-packages/pip/_internal/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 804 bytes .../__pycache__/build_env.cpython-312.pyc | Bin 0 -> 14311 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 12682 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 17683 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 33301 bytes .../__pycache__/main.cpython-312.pyc | Bin 0 -> 687 bytes .../__pycache__/pyproject.cpython-312.pyc | Bin 0 -> 4988 bytes .../self_outdated_check.cpython-312.pyc | Bin 0 -> 10569 bytes .../__pycache__/wheel_builder.cpython-312.pyc | Bin 0 -> 13666 bytes .../site-packages/pip/_internal/build_env.py | 311 + .../site-packages/pip/_internal/cache.py | 290 + .../pip/_internal/cli/__init__.py | 4 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 295 bytes .../autocompletion.cpython-312.pyc | Bin 0 -> 8482 bytes .../__pycache__/base_command.cpython-312.pyc | Bin 0 -> 10472 bytes .../__pycache__/cmdoptions.cpython-312.pyc | Bin 0 -> 30391 bytes .../command_context.cpython-312.pyc | Bin 0 -> 1798 bytes .../cli/__pycache__/main.cpython-312.pyc | Bin 0 -> 2315 bytes .../__pycache__/main_parser.cpython-312.pyc | Bin 0 -> 4922 bytes .../cli/__pycache__/parser.cpython-312.pyc | Bin 0 -> 15039 bytes .../__pycache__/progress_bars.cpython-312.pyc | Bin 0 -> 2637 bytes .../__pycache__/req_command.cpython-312.pyc | Bin 0 -> 18869 bytes .../cli/__pycache__/spinners.cpython-312.pyc | Bin 0 -> 7857 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 392 bytes .../pip/_internal/cli/autocompletion.py | 172 + .../pip/_internal/cli/base_command.py | 236 + .../pip/_internal/cli/cmdoptions.py | 1074 ++ .../pip/_internal/cli/command_context.py | 27 + .../site-packages/pip/_internal/cli/main.py | 79 + .../pip/_internal/cli/main_parser.py | 134 + .../site-packages/pip/_internal/cli/parser.py | 294 + .../pip/_internal/cli/progress_bars.py | 68 + .../pip/_internal/cli/req_command.py | 505 + .../pip/_internal/cli/spinners.py | 159 + .../pip/_internal/cli/status_codes.py | 6 + .../pip/_internal/commands/__init__.py | 132 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4019 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 9728 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 2107 bytes .../__pycache__/completion.cpython-312.pyc | Bin 0 -> 5209 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 13229 bytes .../__pycache__/debug.cpython-312.pyc | Bin 0 -> 10178 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 7606 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 4433 bytes .../commands/__pycache__/hash.cpython-312.pyc | Bin 0 -> 3000 bytes .../commands/__pycache__/help.cpython-312.pyc | Bin 0 -> 1690 bytes .../__pycache__/index.cpython-312.pyc | Bin 0 -> 6737 bytes .../__pycache__/inspect.cpython-312.pyc | Bin 0 -> 3992 bytes .../__pycache__/install.cpython-312.pyc | Bin 0 -> 28930 bytes .../commands/__pycache__/list.cpython-312.pyc | Bin 0 -> 15673 bytes .../__pycache__/search.cpython-312.pyc | Bin 0 -> 7638 bytes .../commands/__pycache__/show.cpython-312.pyc | Bin 0 -> 9745 bytes .../__pycache__/uninstall.cpython-312.pyc | Bin 0 -> 4743 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 8973 bytes .../pip/_internal/commands/cache.py | 225 + .../pip/_internal/commands/check.py | 54 + .../pip/_internal/commands/completion.py | 130 + .../pip/_internal/commands/configuration.py | 280 + .../pip/_internal/commands/debug.py | 201 + .../pip/_internal/commands/download.py | 147 + .../pip/_internal/commands/freeze.py | 109 + .../pip/_internal/commands/hash.py | 59 + .../pip/_internal/commands/help.py | 41 + .../pip/_internal/commands/index.py | 139 + .../pip/_internal/commands/inspect.py | 92 + .../pip/_internal/commands/install.py | 774 ++ .../pip/_internal/commands/list.py | 370 + .../pip/_internal/commands/search.py | 174 + .../pip/_internal/commands/show.py | 189 + .../pip/_internal/commands/uninstall.py | 113 + .../pip/_internal/commands/wheel.py | 183 + .../pip/_internal/configuration.py | 383 + .../pip/_internal/distributions/__init__.py | 21 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 958 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 2879 bytes .../__pycache__/installed.cpython-312.pyc | Bin 0 -> 1717 bytes .../__pycache__/sdist.cpython-312.pyc | Bin 0 -> 8505 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 2265 bytes .../pip/_internal/distributions/base.py | 51 + .../pip/_internal/distributions/installed.py | 29 + .../pip/_internal/distributions/sdist.py | 156 + .../pip/_internal/distributions/wheel.py | 40 + .../site-packages/pip/_internal/exceptions.py | 728 ++ .../pip/_internal/index/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 249 bytes .../__pycache__/collector.cpython-312.pyc | Bin 0 -> 21903 bytes .../package_finder.cpython-312.pyc | Bin 0 -> 40752 bytes .../index/__pycache__/sources.cpython-312.pyc | Bin 0 -> 12621 bytes .../pip/_internal/index/collector.py | 507 + .../pip/_internal/index/package_finder.py | 1027 ++ .../pip/_internal/index/sources.py | 285 + .../pip/_internal/locations/__init__.py | 467 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 16793 bytes .../__pycache__/_distutils.cpython-312.pyc | Bin 0 -> 6873 bytes .../__pycache__/_sysconfig.cpython-312.pyc | Bin 0 -> 8028 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 3798 bytes .../pip/_internal/locations/_distutils.py | 172 + .../pip/_internal/locations/_sysconfig.py | 213 + .../pip/_internal/locations/base.py | 81 + .../site-packages/pip/_internal/main.py | 12 + .../pip/_internal/metadata/__init__.py | 128 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5899 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 2892 bytes .../metadata/__pycache__/base.cpython-312.pyc | Bin 0 -> 35729 bytes .../__pycache__/pkg_resources.cpython-312.pyc | Bin 0 -> 15807 bytes .../pip/_internal/metadata/_json.py | 84 + .../pip/_internal/metadata/base.py | 702 ++ .../_internal/metadata/importlib/__init__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 375 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 3350 bytes .../__pycache__/_dists.cpython-312.pyc | Bin 0 -> 13442 bytes .../__pycache__/_envs.cpython-312.pyc | Bin 0 -> 11197 bytes .../_internal/metadata/importlib/_compat.py | 55 + .../_internal/metadata/importlib/_dists.py | 227 + .../pip/_internal/metadata/importlib/_envs.py | 189 + .../pip/_internal/metadata/pkg_resources.py | 278 + .../pip/_internal/models/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 283 bytes .../__pycache__/candidate.cpython-312.pyc | Bin 0 -> 1922 bytes .../__pycache__/direct_url.cpython-312.pyc | Bin 0 -> 11216 bytes .../format_control.cpython-312.pyc | Bin 0 -> 4244 bytes .../models/__pycache__/index.cpython-312.pyc | Bin 0 -> 1711 bytes .../installation_report.cpython-312.pyc | Bin 0 -> 2289 bytes .../models/__pycache__/link.cpython-312.pyc | Bin 0 -> 26019 bytes .../models/__pycache__/scheme.cpython-312.pyc | Bin 0 -> 1186 bytes .../__pycache__/search_scope.cpython-312.pyc | Bin 0 -> 5105 bytes .../selection_prefs.cpython-312.pyc | Bin 0 -> 1868 bytes .../__pycache__/target_python.cpython-312.pyc | Bin 0 -> 4971 bytes .../models/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 5797 bytes .../pip/_internal/models/candidate.py | 30 + .../pip/_internal/models/direct_url.py | 235 + .../pip/_internal/models/format_control.py | 78 + .../pip/_internal/models/index.py | 28 + .../_internal/models/installation_report.py | 56 + .../pip/_internal/models/link.py | 579 ++ .../pip/_internal/models/scheme.py | 31 + .../pip/_internal/models/search_scope.py | 132 + .../pip/_internal/models/selection_prefs.py | 51 + .../pip/_internal/models/target_python.py | 122 + .../pip/_internal/models/wheel.py | 92 + .../pip/_internal/network/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 271 bytes .../network/__pycache__/auth.cpython-312.pyc | Bin 0 -> 22013 bytes .../network/__pycache__/cache.cpython-312.pyc | Bin 0 -> 6535 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 8570 bytes .../__pycache__/lazy_wheel.cpython-312.pyc | Bin 0 -> 11680 bytes .../__pycache__/session.cpython-312.pyc | Bin 0 -> 18791 bytes .../network/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2270 bytes .../__pycache__/xmlrpc.cpython-312.pyc | Bin 0 -> 2966 bytes .../pip/_internal/network/auth.py | 561 ++ .../pip/_internal/network/cache.py | 106 + .../pip/_internal/network/download.py | 186 + .../pip/_internal/network/lazy_wheel.py | 210 + .../pip/_internal/network/session.py | 520 + .../pip/_internal/network/utils.py | 96 + .../pip/_internal/network/xmlrpc.py | 62 + .../pip/_internal/operations/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 214 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 7596 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 10134 bytes .../__pycache__/prepare.cpython-312.pyc | Bin 0 -> 25764 bytes .../_internal/operations/build/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 220 bytes .../__pycache__/build_tracker.cpython-312.pyc | Bin 0 -> 7840 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 1897 bytes .../metadata_editable.cpython-312.pyc | Bin 0 -> 1931 bytes .../metadata_legacy.cpython-312.pyc | Bin 0 -> 3082 bytes .../build/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 1701 bytes .../wheel_editable.cpython-312.pyc | Bin 0 -> 2042 bytes .../__pycache__/wheel_legacy.cpython-312.pyc | Bin 0 -> 3946 bytes .../operations/build/build_tracker.py | 139 + .../_internal/operations/build/metadata.py | 39 + .../operations/build/metadata_editable.py | 41 + .../operations/build/metadata_legacy.py | 74 + .../pip/_internal/operations/build/wheel.py | 37 + .../operations/build/wheel_editable.py | 46 + .../operations/build/wheel_legacy.py | 102 + .../pip/_internal/operations/check.py | 187 + .../pip/_internal/operations/freeze.py | 255 + .../_internal/operations/install/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 283 bytes .../editable_legacy.cpython-312.pyc | Bin 0 -> 1834 bytes .../install/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 33876 bytes .../operations/install/editable_legacy.py | 46 + .../pip/_internal/operations/install/wheel.py | 734 ++ .../pip/_internal/operations/prepare.py | 730 ++ .../site-packages/pip/_internal/pyproject.py | 179 + .../pip/_internal/req/__init__.py | 92 + .../req/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3760 bytes .../__pycache__/constructors.cpython-312.pyc | Bin 0 -> 21599 bytes .../req/__pycache__/req_file.cpython-312.pyc | Bin 0 -> 21478 bytes .../__pycache__/req_install.cpython-312.pyc | Bin 0 -> 38431 bytes .../req/__pycache__/req_set.cpython-312.pyc | Bin 0 -> 7235 bytes .../__pycache__/req_uninstall.cpython-312.pyc | Bin 0 -> 32994 bytes .../pip/_internal/req/constructors.py | 576 ++ .../pip/_internal/req/req_file.py | 554 ++ .../pip/_internal/req/req_install.py | 923 ++ .../pip/_internal/req/req_set.py | 119 + .../pip/_internal/req/req_uninstall.py | 649 ++ .../pip/_internal/resolution/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 214 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 1202 bytes .../pip/_internal/resolution/base.py | 20 + .../_internal/resolution/legacy/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 221 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 22456 bytes .../_internal/resolution/legacy/resolver.py | 598 ++ .../resolution/resolvelib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 225 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 8354 bytes .../__pycache__/candidates.cpython-312.pyc | Bin 0 -> 30415 bytes .../__pycache__/factory.cpython-312.pyc | Bin 0 -> 32131 bytes .../found_candidates.cpython-312.pyc | Bin 0 -> 6225 bytes .../__pycache__/provider.cpython-312.pyc | Bin 0 -> 10395 bytes .../__pycache__/reporter.cpython-312.pyc | Bin 0 -> 4952 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 11446 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 12368 bytes .../_internal/resolution/resolvelib/base.py | 141 + .../resolution/resolvelib/candidates.py | 597 ++ .../resolution/resolvelib/factory.py | 812 ++ .../resolution/resolvelib/found_candidates.py | 155 + .../resolution/resolvelib/provider.py | 255 + .../resolution/resolvelib/reporter.py | 80 + .../resolution/resolvelib/requirements.py | 166 + .../resolution/resolvelib/resolver.py | 317 + .../pip/_internal/self_outdated_check.py | 248 + .../pip/_internal/utils/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 209 bytes .../__pycache__/_jaraco_text.cpython-312.pyc | Bin 0 -> 4550 bytes .../utils/__pycache__/_log.cpython-312.pyc | Bin 0 -> 1880 bytes .../utils/__pycache__/appdirs.cpython-312.pyc | Bin 0 -> 2424 bytes .../utils/__pycache__/compat.cpython-312.pyc | Bin 0 -> 2227 bytes .../compatibility_tags.cpython-312.pyc | Bin 0 -> 5575 bytes .../__pycache__/datetime.cpython-312.pyc | Bin 0 -> 698 bytes .../__pycache__/deprecation.cpython-312.pyc | Bin 0 -> 4200 bytes .../direct_url_helpers.cpython-312.pyc | Bin 0 -> 3577 bytes .../__pycache__/egg_link.cpython-312.pyc | Bin 0 -> 3240 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2172 bytes .../__pycache__/entrypoints.cpython-312.pyc | Bin 0 -> 4007 bytes .../__pycache__/filesystem.cpython-312.pyc | Bin 0 -> 7472 bytes .../__pycache__/filetypes.cpython-312.pyc | Bin 0 -> 1178 bytes .../utils/__pycache__/glibc.cpython-312.pyc | Bin 0 -> 2356 bytes .../utils/__pycache__/hashes.cpython-312.pyc | Bin 0 -> 7568 bytes .../utils/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13571 bytes .../utils/__pycache__/misc.cpython-312.pyc | Bin 0 -> 34135 bytes .../utils/__pycache__/models.cpython-312.pyc | Bin 0 -> 2726 bytes .../__pycache__/packaging.cpython-312.pyc | Bin 0 -> 2597 bytes .../setuptools_build.cpython-312.pyc | Bin 0 -> 4564 bytes .../__pycache__/subprocess.cpython-312.pyc | Bin 0 -> 8732 bytes .../__pycache__/temp_dir.cpython-312.pyc | Bin 0 -> 12076 bytes .../__pycache__/unpacking.cpython-312.pyc | Bin 0 -> 11122 bytes .../utils/__pycache__/urls.cpython-312.pyc | Bin 0 -> 2419 bytes .../__pycache__/virtualenv.cpython-312.pyc | Bin 0 -> 4494 bytes .../utils/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 5940 bytes .../pip/_internal/utils/_jaraco_text.py | 109 + .../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/egg_link.py | 80 + .../pip/_internal/utils/encoding.py | 36 + .../pip/_internal/utils/entrypoints.py | 84 + .../pip/_internal/utils/filesystem.py | 153 + .../pip/_internal/utils/filetypes.py | 27 + .../pip/_internal/utils/glibc.py | 88 + .../pip/_internal/utils/hashes.py | 151 + .../pip/_internal/utils/logging.py | 348 + .../site-packages/pip/_internal/utils/misc.py | 783 ++ .../pip/_internal/utils/models.py | 39 + .../pip/_internal/utils/packaging.py | 57 + .../pip/_internal/utils/setuptools_build.py | 146 + .../pip/_internal/utils/subprocess.py | 260 + .../pip/_internal/utils/temp_dir.py | 296 + .../pip/_internal/utils/unpacking.py | 257 + .../site-packages/pip/_internal/utils/urls.py | 62 + .../pip/_internal/utils/virtualenv.py | 104 + .../pip/_internal/utils/wheel.py | 134 + .../pip/_internal/vcs/__init__.py | 15 + .../vcs/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 548 bytes .../vcs/__pycache__/bazaar.cpython-312.pyc | Bin 0 -> 5040 bytes .../vcs/__pycache__/git.cpython-312.pyc | Bin 0 -> 19009 bytes .../vcs/__pycache__/mercurial.cpython-312.pyc | Bin 0 -> 7629 bytes .../__pycache__/subversion.cpython-312.pyc | Bin 0 -> 12501 bytes .../versioncontrol.cpython-312.pyc | Bin 0 -> 29027 bytes .../site-packages/pip/_internal/vcs/bazaar.py | 112 + .../site-packages/pip/_internal/vcs/git.py | 526 + .../pip/_internal/vcs/mercurial.py | 163 + .../pip/_internal/vcs/subversion.py | 324 + .../pip/_internal/vcs/versioncontrol.py | 705 ++ .../pip/_internal/wheel_builder.py | 354 + .../site-packages/pip/_vendor/__init__.py | 121 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4710 bytes .../_vendor/__pycache__/six.cpython-312.pyc | Bin 0 -> 41287 bytes .../typing_extensions.cpython-312.pyc | Bin 0 -> 122067 bytes .../pip/_vendor/cachecontrol/__init__.py | 28 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 920 bytes .../__pycache__/_cmd.cpython-312.pyc | Bin 0 -> 2664 bytes .../__pycache__/adapter.cpython-312.pyc | Bin 0 -> 6482 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 3827 bytes .../__pycache__/controller.cpython-312.pyc | Bin 0 -> 16185 bytes .../__pycache__/filewrapper.cpython-312.pyc | Bin 0 -> 4365 bytes .../__pycache__/heuristics.cpython-312.pyc | Bin 0 -> 6712 bytes .../__pycache__/serialize.cpython-312.pyc | Bin 0 -> 6423 bytes .../__pycache__/wrapper.cpython-312.pyc | Bin 0 -> 1692 bytes .../pip/_vendor/cachecontrol/_cmd.py | 70 + .../pip/_vendor/cachecontrol/adapter.py | 161 + .../pip/_vendor/cachecontrol/cache.py | 74 + .../_vendor/cachecontrol/caches/__init__.py | 8 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 453 bytes .../__pycache__/file_cache.cpython-312.pyc | Bin 0 -> 7728 bytes .../__pycache__/redis_cache.cpython-312.pyc | Bin 0 -> 2756 bytes .../_vendor/cachecontrol/caches/file_cache.py | 181 + .../cachecontrol/caches/redis_cache.py | 48 + .../pip/_vendor/cachecontrol/controller.py | 494 + .../pip/_vendor/cachecontrol/filewrapper.py | 119 + .../pip/_vendor/cachecontrol/heuristics.py | 154 + .../pip/_vendor/cachecontrol/serialize.py | 206 + .../pip/_vendor/cachecontrol/wrapper.py | 43 + .../pip/_vendor/certifi/__init__.py | 4 + .../pip/_vendor/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 336 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 663 bytes .../certifi/__pycache__/core.cpython-312.pyc | Bin 0 -> 3345 bytes .../pip/_vendor/certifi/cacert.pem | 4635 +++++++++ .../site-packages/pip/_vendor/certifi/core.py | 119 + .../pip/_vendor/chardet/__init__.py | 115 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4586 bytes .../__pycache__/big5freq.cpython-312.pyc | Bin 0 -> 27217 bytes .../__pycache__/big5prober.cpython-312.pyc | Bin 0 -> 1405 bytes .../chardistribution.cpython-312.pyc | Bin 0 -> 9656 bytes .../charsetgroupprober.cpython-312.pyc | Bin 0 -> 4140 bytes .../__pycache__/charsetprober.cpython-312.pyc | Bin 0 -> 5036 bytes .../codingstatemachine.cpython-312.pyc | Bin 0 -> 3896 bytes .../codingstatemachinedict.cpython-312.pyc | Bin 0 -> 807 bytes .../__pycache__/cp949prober.cpython-312.pyc | Bin 0 -> 1414 bytes .../chardet/__pycache__/enums.cpython-312.pyc | Bin 0 -> 3014 bytes .../__pycache__/escprober.cpython-312.pyc | Bin 0 -> 4584 bytes .../chardet/__pycache__/escsm.cpython-312.pyc | Bin 0 -> 15328 bytes .../__pycache__/eucjpprober.cpython-312.pyc | Bin 0 -> 4401 bytes .../__pycache__/euckrfreq.cpython-312.pyc | Bin 0 -> 12100 bytes .../__pycache__/euckrprober.cpython-312.pyc | Bin 0 -> 1408 bytes .../__pycache__/euctwfreq.cpython-312.pyc | Bin 0 -> 27222 bytes .../__pycache__/euctwprober.cpython-312.pyc | Bin 0 -> 1408 bytes .../__pycache__/gb2312freq.cpython-312.pyc | Bin 0 -> 19144 bytes .../__pycache__/gb2312prober.cpython-312.pyc | Bin 0 -> 1421 bytes .../__pycache__/hebrewprober.cpython-312.pyc | Bin 0 -> 5840 bytes .../__pycache__/jisfreq.cpython-312.pyc | Bin 0 -> 22173 bytes .../__pycache__/johabfreq.cpython-312.pyc | Bin 0 -> 83021 bytes .../__pycache__/johabprober.cpython-312.pyc | Bin 0 -> 1412 bytes .../__pycache__/jpcntx.cpython-312.pyc | Bin 0 -> 39567 bytes .../langbulgarianmodel.cpython-312.pyc | Bin 0 -> 83140 bytes .../langgreekmodel.cpython-312.pyc | Bin 0 -> 77006 bytes .../langhebrewmodel.cpython-312.pyc | Bin 0 -> 77517 bytes .../langhungarianmodel.cpython-312.pyc | Bin 0 -> 83094 bytes .../langrussianmodel.cpython-312.pyc | Bin 0 -> 105269 bytes .../__pycache__/langthaimodel.cpython-312.pyc | Bin 0 -> 77695 bytes .../langturkishmodel.cpython-312.pyc | Bin 0 -> 77534 bytes .../__pycache__/latin1prober.cpython-312.pyc | Bin 0 -> 7020 bytes .../macromanprober.cpython-312.pyc | Bin 0 -> 7200 bytes .../mbcharsetprober.cpython-312.pyc | Bin 0 -> 3921 bytes .../mbcsgroupprober.cpython-312.pyc | Bin 0 -> 1606 bytes .../__pycache__/mbcssm.cpython-312.pyc | Bin 0 -> 38663 bytes .../__pycache__/resultdict.cpython-312.pyc | Bin 0 -> 650 bytes .../sbcharsetprober.cpython-312.pyc | Bin 0 -> 6405 bytes .../sbcsgroupprober.cpython-312.pyc | Bin 0 -> 2375 bytes .../__pycache__/sjisprober.cpython-312.pyc | Bin 0 -> 4513 bytes .../universaldetector.cpython-312.pyc | Bin 0 -> 12287 bytes .../__pycache__/utf1632prober.cpython-312.pyc | Bin 0 -> 9997 bytes .../__pycache__/utf8prober.cpython-312.pyc | Bin 0 -> 3193 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 506 bytes .../pip/_vendor/chardet/big5freq.py | 386 + .../pip/_vendor/chardet/big5prober.py | 47 + .../pip/_vendor/chardet/chardistribution.py | 261 + .../pip/_vendor/chardet/charsetgroupprober.py | 106 + .../pip/_vendor/chardet/charsetprober.py | 147 + .../pip/_vendor/chardet/cli/__init__.py | 0 .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 213 bytes .../__pycache__/chardetect.cpython-312.pyc | Bin 0 -> 4030 bytes .../pip/_vendor/chardet/cli/chardetect.py | 112 + .../pip/_vendor/chardet/codingstatemachine.py | 90 + .../_vendor/chardet/codingstatemachinedict.py | 19 + .../pip/_vendor/chardet/cp949prober.py | 49 + .../pip/_vendor/chardet/enums.py | 85 + .../pip/_vendor/chardet/escprober.py | 102 + .../pip/_vendor/chardet/escsm.py | 261 + .../pip/_vendor/chardet/eucjpprober.py | 102 + .../pip/_vendor/chardet/euckrfreq.py | 196 + .../pip/_vendor/chardet/euckrprober.py | 47 + .../pip/_vendor/chardet/euctwfreq.py | 388 + .../pip/_vendor/chardet/euctwprober.py | 47 + .../pip/_vendor/chardet/gb2312freq.py | 284 + .../pip/_vendor/chardet/gb2312prober.py | 47 + .../pip/_vendor/chardet/hebrewprober.py | 316 + .../pip/_vendor/chardet/jisfreq.py | 325 + .../pip/_vendor/chardet/johabfreq.py | 2382 +++++ .../pip/_vendor/chardet/johabprober.py | 47 + .../pip/_vendor/chardet/jpcntx.py | 238 + .../pip/_vendor/chardet/langbulgarianmodel.py | 4649 +++++++++ .../pip/_vendor/chardet/langgreekmodel.py | 4397 +++++++++ .../pip/_vendor/chardet/langhebrewmodel.py | 4380 +++++++++ .../pip/_vendor/chardet/langhungarianmodel.py | 4649 +++++++++ .../pip/_vendor/chardet/langrussianmodel.py | 5725 +++++++++++ .../pip/_vendor/chardet/langthaimodel.py | 4380 +++++++++ .../pip/_vendor/chardet/langturkishmodel.py | 4380 +++++++++ .../pip/_vendor/chardet/latin1prober.py | 147 + .../pip/_vendor/chardet/macromanprober.py | 162 + .../pip/_vendor/chardet/mbcharsetprober.py | 95 + .../pip/_vendor/chardet/mbcsgroupprober.py | 57 + .../pip/_vendor/chardet/mbcssm.py | 661 ++ .../pip/_vendor/chardet/metadata/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 218 bytes .../__pycache__/languages.cpython-312.pyc | Bin 0 -> 9773 bytes .../pip/_vendor/chardet/metadata/languages.py | 352 + .../pip/_vendor/chardet/resultdict.py | 16 + .../pip/_vendor/chardet/sbcharsetprober.py | 162 + .../pip/_vendor/chardet/sbcsgroupprober.py | 88 + .../pip/_vendor/chardet/sjisprober.py | 105 + .../pip/_vendor/chardet/universaldetector.py | 362 + .../pip/_vendor/chardet/utf1632prober.py | 225 + .../pip/_vendor/chardet/utf8prober.py | 82 + .../pip/_vendor/chardet/version.py | 9 + .../pip/_vendor/colorama/__init__.py | 7 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 510 bytes .../colorama/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 3968 bytes .../__pycache__/ansitowin32.cpython-312.pyc | Bin 0 -> 16439 bytes .../__pycache__/initialise.cpython-312.pyc | Bin 0 -> 3568 bytes .../__pycache__/win32.cpython-312.pyc | Bin 0 -> 8144 bytes .../__pycache__/winterm.cpython-312.pyc | Bin 0 -> 9106 bytes .../pip/_vendor/colorama/ansi.py | 102 + .../pip/_vendor/colorama/ansitowin32.py | 277 + .../pip/_vendor/colorama/initialise.py | 121 + .../pip/_vendor/colorama/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 216 bytes .../__pycache__/ansi_test.cpython-312.pyc | Bin 0 -> 5485 bytes .../ansitowin32_test.cpython-312.pyc | Bin 0 -> 18121 bytes .../initialise_test.cpython-312.pyc | Bin 0 -> 11766 bytes .../__pycache__/isatty_test.cpython-312.pyc | Bin 0 -> 4922 bytes .../tests/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2506 bytes .../__pycache__/winterm_test.cpython-312.pyc | Bin 0 -> 6630 bytes .../pip/_vendor/colorama/tests/ansi_test.py | 76 + .../colorama/tests/ansitowin32_test.py | 294 + .../_vendor/colorama/tests/initialise_test.py | 189 + .../pip/_vendor/colorama/tests/isatty_test.py | 57 + .../pip/_vendor/colorama/tests/utils.py | 49 + .../_vendor/colorama/tests/winterm_test.py | 131 + .../pip/_vendor/colorama/win32.py | 180 + .../pip/_vendor/colorama/winterm.py | 195 + .../pip/_vendor/distlib/__init__.py | 33 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1287 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 45623 bytes .../__pycache__/database.cpython-312.pyc | Bin 0 -> 66045 bytes .../distlib/__pycache__/index.cpython-312.pyc | Bin 0 -> 24384 bytes .../__pycache__/locators.cpython-312.pyc | Bin 0 -> 60176 bytes .../__pycache__/manifest.cpython-312.pyc | Bin 0 -> 15143 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 7700 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 41817 bytes .../__pycache__/resources.cpython-312.pyc | Bin 0 -> 17343 bytes .../__pycache__/scripts.cpython-312.pyc | Bin 0 -> 19598 bytes .../distlib/__pycache__/util.cpython-312.pyc | Bin 0 -> 88274 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 30384 bytes .../distlib/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 51879 bytes .../pip/_vendor/distlib/compat.py | 1138 +++ .../pip/_vendor/distlib/database.py | 1359 +++ .../pip/_vendor/distlib/index.py | 508 + .../pip/_vendor/distlib/locators.py | 1303 +++ .../pip/_vendor/distlib/manifest.py | 384 + .../pip/_vendor/distlib/markers.py | 167 + .../pip/_vendor/distlib/metadata.py | 1068 ++ .../pip/_vendor/distlib/resources.py | 358 + .../pip/_vendor/distlib/scripts.py | 452 + .../site-packages/pip/_vendor/distlib/util.py | 2025 ++++ .../pip/_vendor/distlib/version.py | 751 ++ .../pip/_vendor/distlib/wheel.py | 1099 +++ .../pip/_vendor/distro/__init__.py | 54 + .../pip/_vendor/distro/__main__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 978 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 310 bytes .../distro/__pycache__/distro.cpython-312.pyc | Bin 0 -> 53772 bytes .../pip/_vendor/distro/distro.py | 1399 +++ .../pip/_vendor/idna/__init__.py | 44 + .../idna/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 899 bytes .../idna/__pycache__/codec.cpython-312.pyc | Bin 0 -> 4651 bytes .../idna/__pycache__/compat.cpython-312.pyc | Bin 0 -> 905 bytes .../idna/__pycache__/core.cpython-312.pyc | Bin 0 -> 16041 bytes .../idna/__pycache__/idnadata.cpython-312.pyc | Bin 0 -> 99515 bytes .../__pycache__/intranges.cpython-312.pyc | Bin 0 -> 2656 bytes .../__pycache__/package_data.cpython-312.pyc | Bin 0 -> 234 bytes .../__pycache__/uts46data.cpython-312.pyc | Bin 0 -> 158888 bytes .../site-packages/pip/_vendor/idna/codec.py | 112 + .../site-packages/pip/_vendor/idna/compat.py | 13 + .../site-packages/pip/_vendor/idna/core.py | 400 + .../pip/_vendor/idna/idnadata.py | 4246 ++++++++ .../pip/_vendor/idna/intranges.py | 54 + .../pip/_vendor/idna/package_data.py | 2 + .../pip/_vendor/idna/uts46data.py | 8600 +++++++++++++++++ .../pip/_vendor/msgpack/__init__.py | 57 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1849 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 2043 bytes .../msgpack/__pycache__/ext.cpython-312.pyc | Bin 0 -> 8686 bytes .../__pycache__/fallback.cpython-312.pyc | Bin 0 -> 43594 bytes .../pip/_vendor/msgpack/exceptions.py | 48 + .../site-packages/pip/_vendor/msgpack/ext.py | 193 + .../pip/_vendor/msgpack/fallback.py | 1010 ++ .../pip/_vendor/packaging/__about__.py | 26 + .../pip/_vendor/packaging/__init__.py | 25 + .../__pycache__/__about__.cpython-312.pyc | Bin 0 -> 648 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 484 bytes .../__pycache__/_manylinux.cpython-312.pyc | Bin 0 -> 12094 bytes .../__pycache__/_musllinux.cpython-312.pyc | Bin 0 -> 6928 bytes .../__pycache__/_structures.cpython-312.pyc | Bin 0 -> 3259 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 14076 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 6964 bytes .../__pycache__/specifiers.cpython-312.pyc | Bin 0 -> 31265 bytes .../__pycache__/tags.cpython-312.pyc | Bin 0 -> 18974 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 5886 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 19957 bytes .../pip/_vendor/packaging/_manylinux.py | 301 + .../pip/_vendor/packaging/_musllinux.py | 136 + .../pip/_vendor/packaging/_structures.py | 61 + .../pip/_vendor/packaging/markers.py | 304 + .../pip/_vendor/packaging/requirements.py | 146 + .../pip/_vendor/packaging/specifiers.py | 802 ++ .../pip/_vendor/packaging/tags.py | 487 + .../pip/_vendor/packaging/utils.py | 136 + .../pip/_vendor/packaging/version.py | 504 + .../pip/_vendor/pkg_resources/__init__.py | 3361 +++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 146492 bytes .../pip/_vendor/platformdirs/__init__.py | 566 ++ .../pip/_vendor/platformdirs/__main__.py | 53 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 18047 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 1964 bytes .../__pycache__/android.cpython-312.pyc | Bin 0 -> 9462 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 9690 bytes .../__pycache__/macos.cpython-312.pyc | Bin 0 -> 5655 bytes .../__pycache__/unix.cpython-312.pyc | Bin 0 -> 12459 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 329 bytes .../__pycache__/windows.cpython-312.pyc | Bin 0 -> 13017 bytes .../pip/_vendor/platformdirs/android.py | 210 + .../pip/_vendor/platformdirs/api.py | 223 + .../pip/_vendor/platformdirs/macos.py | 91 + .../pip/_vendor/platformdirs/unix.py | 223 + .../pip/_vendor/platformdirs/version.py | 4 + .../pip/_vendor/platformdirs/windows.py | 255 + .../pip/_vendor/pygments/__init__.py | 82 + .../pip/_vendor/pygments/__main__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3507 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 753 bytes .../__pycache__/cmdline.cpython-312.pyc | Bin 0 -> 26624 bytes .../__pycache__/console.cpython-312.pyc | Bin 0 -> 2645 bytes .../__pycache__/filter.cpython-312.pyc | Bin 0 -> 3251 bytes .../__pycache__/formatter.cpython-312.pyc | Bin 0 -> 4588 bytes .../__pycache__/lexer.cpython-312.pyc | Bin 0 -> 38348 bytes .../__pycache__/modeline.cpython-312.pyc | Bin 0 -> 1587 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 3415 bytes .../__pycache__/regexopt.cpython-312.pyc | Bin 0 -> 4100 bytes .../__pycache__/scanner.cpython-312.pyc | Bin 0 -> 4775 bytes .../__pycache__/sphinxext.cpython-312.pyc | Bin 0 -> 11065 bytes .../__pycache__/style.cpython-312.pyc | Bin 0 -> 6693 bytes .../__pycache__/token.cpython-312.pyc | Bin 0 -> 8161 bytes .../__pycache__/unistring.cpython-312.pyc | Bin 0 -> 33007 bytes .../pygments/__pycache__/util.cpython-312.pyc | Bin 0 -> 14000 bytes .../pip/_vendor/pygments/cmdline.py | 668 ++ .../pip/_vendor/pygments/console.py | 70 + .../pip/_vendor/pygments/filter.py | 71 + .../pip/_vendor/pygments/filters/__init__.py | 940 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 37955 bytes .../pip/_vendor/pygments/formatter.py | 124 + .../_vendor/pygments/formatters/__init__.py | 158 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6945 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 4234 bytes .../__pycache__/bbcode.cpython-312.pyc | Bin 0 -> 4213 bytes .../__pycache__/groff.cpython-312.pyc | Bin 0 -> 7283 bytes .../__pycache__/html.cpython-312.pyc | Bin 0 -> 40591 bytes .../__pycache__/img.cpython-312.pyc | Bin 0 -> 27062 bytes .../__pycache__/irc.cpython-312.pyc | Bin 0 -> 6084 bytes .../__pycache__/latex.cpython-312.pyc | Bin 0 -> 19973 bytes .../__pycache__/other.cpython-312.pyc | Bin 0 -> 6903 bytes .../__pycache__/pangomarkup.cpython-312.pyc | Bin 0 -> 2949 bytes .../__pycache__/rtf.cpython-312.pyc | Bin 0 -> 6145 bytes .../__pycache__/svg.cpython-312.pyc | Bin 0 -> 9085 bytes .../__pycache__/terminal.cpython-312.pyc | Bin 0 -> 5848 bytes .../__pycache__/terminal256.cpython-312.pyc | Bin 0 -> 15176 bytes .../_vendor/pygments/formatters/_mapping.py | 23 + .../pip/_vendor/pygments/formatters/bbcode.py | 108 + .../pip/_vendor/pygments/formatters/groff.py | 170 + .../pip/_vendor/pygments/formatters/html.py | 989 ++ .../pip/_vendor/pygments/formatters/img.py | 645 ++ .../pip/_vendor/pygments/formatters/irc.py | 154 + .../pip/_vendor/pygments/formatters/latex.py | 521 + .../pip/_vendor/pygments/formatters/other.py | 161 + .../pygments/formatters/pangomarkup.py | 83 + .../pip/_vendor/pygments/formatters/rtf.py | 146 + .../pip/_vendor/pygments/formatters/svg.py | 188 + .../_vendor/pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + .../pip/_vendor/pygments/lexer.py | 943 ++ .../pip/_vendor/pygments/lexers/__init__.py | 362 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 14671 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 64423 bytes .../lexers/__pycache__/python.cpython-312.pyc | Bin 0 -> 42658 bytes .../pip/_vendor/pygments/lexers/_mapping.py | 559 ++ .../pip/_vendor/pygments/lexers/python.py | 1198 +++ .../pip/_vendor/pygments/modeline.py | 43 + .../pip/_vendor/pygments/plugin.py | 88 + .../pip/_vendor/pygments/regexopt.py | 91 + .../pip/_vendor/pygments/scanner.py | 104 + .../pip/_vendor/pygments/sphinxext.py | 217 + .../pip/_vendor/pygments/style.py | 197 + .../pip/_vendor/pygments/styles/__init__.py | 103 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4467 bytes .../pip/_vendor/pygments/token.py | 213 + .../pip/_vendor/pygments/unistring.py | 153 + .../pip/_vendor/pygments/util.py | 330 + .../pip/_vendor/pyparsing/__init__.py | 322 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7930 bytes .../__pycache__/actions.cpython-312.pyc | Bin 0 -> 8414 bytes .../__pycache__/common.cpython-312.pyc | Bin 0 -> 13433 bytes .../__pycache__/core.cpython-312.pyc | Bin 0 -> 267727 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 13013 bytes .../__pycache__/helpers.cpython-312.pyc | Bin 0 -> 48520 bytes .../__pycache__/results.cpython-312.pyc | Bin 0 -> 34129 bytes .../__pycache__/testing.cpython-312.pyc | Bin 0 -> 17207 bytes .../__pycache__/unicode.cpython-312.pyc | Bin 0 -> 13203 bytes .../__pycache__/util.cpython-312.pyc | Bin 0 -> 14923 bytes .../pip/_vendor/pyparsing/actions.py | 217 + .../pip/_vendor/pyparsing/common.py | 432 + .../pip/_vendor/pyparsing/core.py | 6115 ++++++++++++ .../pip/_vendor/pyparsing/diagram/__init__.py | 656 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 26832 bytes .../pip/_vendor/pyparsing/exceptions.py | 299 + .../pip/_vendor/pyparsing/helpers.py | 1100 +++ .../pip/_vendor/pyparsing/results.py | 796 ++ .../pip/_vendor/pyparsing/testing.py | 331 + .../pip/_vendor/pyparsing/unicode.py | 361 + .../pip/_vendor/pyparsing/util.py | 284 + .../pip/_vendor/pyproject_hooks/__init__.py | 23 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 632 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 393 bytes .../__pycache__/_impl.cpython-312.pyc | Bin 0 -> 14744 bytes .../pip/_vendor/pyproject_hooks/_compat.py | 8 + .../pip/_vendor/pyproject_hooks/_impl.py | 330 + .../pyproject_hooks/_in_process/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1099 bytes .../__pycache__/_in_process.cpython-312.pyc | Bin 0 -> 14416 bytes .../_in_process/_in_process.py | 353 + .../pip/_vendor/requests/__init__.py | 182 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5472 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 603 bytes .../_internal_utils.cpython-312.pyc | Bin 0 -> 2043 bytes .../__pycache__/adapters.cpython-312.pyc | Bin 0 -> 21299 bytes .../requests/__pycache__/api.cpython-312.pyc | Bin 0 -> 7223 bytes .../requests/__pycache__/auth.cpython-312.pyc | Bin 0 -> 13942 bytes .../__pycache__/certs.cpython-312.pyc | Bin 0 -> 941 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 1526 bytes .../__pycache__/cookies.cpython-312.pyc | Bin 0 -> 25265 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7066 bytes .../requests/__pycache__/help.cpython-312.pyc | Bin 0 -> 4331 bytes .../__pycache__/hooks.cpython-312.pyc | Bin 0 -> 1071 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 35467 bytes .../__pycache__/packages.cpython-312.pyc | Bin 0 -> 791 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 27776 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 5978 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 5636 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 36094 bytes .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 50 + .../pip/_vendor/requests/adapters.py | 538 ++ .../site-packages/pip/_vendor/requests/api.py | 157 + .../pip/_vendor/requests/auth.py | 315 + .../pip/_vendor/requests/certs.py | 24 + .../pip/_vendor/requests/compat.py | 67 + .../pip/_vendor/requests/cookies.py | 561 ++ .../pip/_vendor/requests/exceptions.py | 141 + .../pip/_vendor/requests/help.py | 131 + .../pip/_vendor/requests/hooks.py | 33 + .../pip/_vendor/requests/models.py | 1034 ++ .../pip/_vendor/requests/packages.py | 16 + .../pip/_vendor/requests/sessions.py | 833 ++ .../pip/_vendor/requests/status_codes.py | 128 + .../pip/_vendor/requests/structures.py | 99 + .../pip/_vendor/requests/utils.py | 1088 +++ .../pip/_vendor/resolvelib/__init__.py | 26 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 653 bytes .../__pycache__/providers.cpython-312.pyc | Bin 0 -> 6870 bytes .../__pycache__/reporters.cpython-312.pyc | Bin 0 -> 2673 bytes .../__pycache__/resolvers.cpython-312.pyc | Bin 0 -> 25916 bytes .../__pycache__/structs.cpython-312.pyc | Bin 0 -> 10525 bytes .../pip/_vendor/resolvelib/compat/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 219 bytes .../collections_abc.cpython-312.pyc | Bin 0 -> 439 bytes .../resolvelib/compat/collections_abc.py | 6 + .../pip/_vendor/resolvelib/providers.py | 133 + .../pip/_vendor/resolvelib/reporters.py | 43 + .../pip/_vendor/resolvelib/resolvers.py | 547 ++ .../pip/_vendor/resolvelib/structs.py | 170 + .../pip/_vendor/rich/__init__.py | 177 + .../pip/_vendor/rich/__main__.py | 274 + .../rich/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7034 bytes .../rich/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 10323 bytes .../__pycache__/_cell_widths.cpython-312.pyc | Bin 0 -> 7840 bytes .../__pycache__/_emoji_codes.cpython-312.pyc | Bin 0 -> 205995 bytes .../_emoji_replace.cpython-312.pyc | Bin 0 -> 1748 bytes .../_export_format.cpython-312.pyc | Bin 0 -> 2340 bytes .../__pycache__/_extension.cpython-312.pyc | Bin 0 -> 556 bytes .../rich/__pycache__/_fileno.cpython-312.pyc | Bin 0 -> 874 bytes .../rich/__pycache__/_inspect.cpython-312.pyc | Bin 0 -> 12096 bytes .../__pycache__/_log_render.cpython-312.pyc | Bin 0 -> 4166 bytes .../rich/__pycache__/_loop.cpython-312.pyc | Bin 0 -> 1904 bytes .../__pycache__/_null_file.cpython-312.pyc | Bin 0 -> 3639 bytes .../__pycache__/_palettes.cpython-312.pyc | Bin 0 -> 5179 bytes .../rich/__pycache__/_pick.cpython-312.pyc | Bin 0 -> 745 bytes .../rich/__pycache__/_ratio.cpython-312.pyc | Bin 0 -> 6598 bytes .../__pycache__/_spinners.cpython-312.pyc | Bin 0 -> 13198 bytes .../rich/__pycache__/_stack.cpython-312.pyc | Bin 0 -> 984 bytes .../rich/__pycache__/_timer.cpython-312.pyc | Bin 0 -> 884 bytes .../_win32_console.cpython-312.pyc | Bin 0 -> 28995 bytes .../rich/__pycache__/_windows.cpython-312.pyc | Bin 0 -> 2509 bytes .../_windows_renderer.cpython-312.pyc | Bin 0 -> 3592 bytes .../rich/__pycache__/_wrap.cpython-312.pyc | Bin 0 -> 2379 bytes .../rich/__pycache__/abc.cpython-312.pyc | Bin 0 -> 1627 bytes .../rich/__pycache__/align.cpython-312.pyc | Bin 0 -> 12341 bytes .../rich/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 9125 bytes .../rich/__pycache__/bar.cpython-312.pyc | Bin 0 -> 4291 bytes .../rich/__pycache__/box.cpython-312.pyc | Bin 0 -> 11877 bytes .../rich/__pycache__/cells.cpython-312.pyc | Bin 0 -> 5637 bytes .../rich/__pycache__/color.cpython-312.pyc | Bin 0 -> 26589 bytes .../__pycache__/color_triplet.cpython-312.pyc | Bin 0 -> 1720 bytes .../rich/__pycache__/columns.cpython-312.pyc | Bin 0 -> 8606 bytes .../rich/__pycache__/console.cpython-312.pyc | Bin 0 -> 113812 bytes .../__pycache__/constrain.cpython-312.pyc | Bin 0 -> 2277 bytes .../__pycache__/containers.cpython-312.pyc | Bin 0 -> 9245 bytes .../rich/__pycache__/control.cpython-312.pyc | Bin 0 -> 10948 bytes .../default_styles.cpython-312.pyc | Bin 0 -> 10392 bytes .../rich/__pycache__/diagnose.cpython-312.pyc | Bin 0 -> 1506 bytes .../rich/__pycache__/emoji.cpython-312.pyc | Bin 0 -> 4228 bytes .../rich/__pycache__/errors.cpython-312.pyc | Bin 0 -> 1864 bytes .../__pycache__/file_proxy.cpython-312.pyc | Bin 0 -> 3596 bytes .../rich/__pycache__/filesize.cpython-312.pyc | Bin 0 -> 3101 bytes .../__pycache__/highlighter.cpython-312.pyc | Bin 0 -> 9917 bytes .../rich/__pycache__/json.cpython-312.pyc | Bin 0 -> 6054 bytes .../rich/__pycache__/jupyter.cpython-312.pyc | Bin 0 -> 5228 bytes .../rich/__pycache__/layout.cpython-312.pyc | Bin 0 -> 20239 bytes .../rich/__pycache__/live.cpython-312.pyc | Bin 0 -> 19162 bytes .../__pycache__/live_render.cpython-312.pyc | Bin 0 -> 4913 bytes .../rich/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13573 bytes .../rich/__pycache__/markup.cpython-312.pyc | Bin 0 -> 9317 bytes .../rich/__pycache__/measure.cpython-312.pyc | Bin 0 -> 6395 bytes .../rich/__pycache__/padding.cpython-312.pyc | Bin 0 -> 7153 bytes .../rich/__pycache__/pager.cpython-312.pyc | Bin 0 -> 1839 bytes .../rich/__pycache__/palette.cpython-312.pyc | Bin 0 -> 5333 bytes .../rich/__pycache__/panel.cpython-312.pyc | Bin 0 -> 12116 bytes .../rich/__pycache__/pretty.cpython-312.pyc | Bin 0 -> 40075 bytes .../rich/__pycache__/progress.cpython-312.pyc | Bin 0 -> 75097 bytes .../__pycache__/progress_bar.cpython-312.pyc | Bin 0 -> 10408 bytes .../rich/__pycache__/prompt.cpython-312.pyc | Bin 0 -> 14800 bytes .../rich/__pycache__/protocol.cpython-312.pyc | Bin 0 -> 1811 bytes .../rich/__pycache__/region.cpython-312.pyc | Bin 0 -> 586 bytes .../rich/__pycache__/repr.cpython-312.pyc | Bin 0 -> 6645 bytes .../rich/__pycache__/rule.cpython-312.pyc | Bin 0 -> 6587 bytes .../rich/__pycache__/scope.cpython-312.pyc | Bin 0 -> 3849 bytes .../rich/__pycache__/screen.cpython-312.pyc | Bin 0 -> 2503 bytes .../rich/__pycache__/segment.cpython-312.pyc | Bin 0 -> 28180 bytes .../rich/__pycache__/spinner.cpython-312.pyc | Bin 0 -> 6083 bytes .../rich/__pycache__/status.cpython-312.pyc | Bin 0 -> 6087 bytes .../rich/__pycache__/style.cpython-312.pyc | Bin 0 -> 33533 bytes .../rich/__pycache__/styled.cpython-312.pyc | Bin 0 -> 2158 bytes .../rich/__pycache__/syntax.cpython-312.pyc | Bin 0 -> 39631 bytes .../rich/__pycache__/table.cpython-312.pyc | Bin 0 -> 43603 bytes .../terminal_theme.cpython-312.pyc | Bin 0 -> 3367 bytes .../rich/__pycache__/text.cpython-312.pyc | Bin 0 -> 58982 bytes .../rich/__pycache__/theme.cpython-312.pyc | Bin 0 -> 6359 bytes .../rich/__pycache__/themes.cpython-312.pyc | Bin 0 -> 333 bytes .../__pycache__/traceback.cpython-312.pyc | Bin 0 -> 31567 bytes .../rich/__pycache__/tree.cpython-312.pyc | Bin 0 -> 11458 bytes .../pip/_vendor/rich/_cell_widths.py | 451 + .../pip/_vendor/rich/_emoji_codes.py | 3610 +++++++ .../pip/_vendor/rich/_emoji_replace.py | 32 + .../pip/_vendor/rich/_export_format.py | 76 + .../pip/_vendor/rich/_extension.py | 10 + .../site-packages/pip/_vendor/rich/_fileno.py | 24 + .../pip/_vendor/rich/_inspect.py | 270 + .../pip/_vendor/rich/_log_render.py | 94 + .../site-packages/pip/_vendor/rich/_loop.py | 43 + .../pip/_vendor/rich/_null_file.py | 69 + .../pip/_vendor/rich/_palettes.py | 309 + .../site-packages/pip/_vendor/rich/_pick.py | 17 + .../site-packages/pip/_vendor/rich/_ratio.py | 160 + .../pip/_vendor/rich/_spinners.py | 482 + .../site-packages/pip/_vendor/rich/_stack.py | 16 + .../site-packages/pip/_vendor/rich/_timer.py | 19 + .../pip/_vendor/rich/_win32_console.py | 662 ++ .../pip/_vendor/rich/_windows.py | 72 + .../pip/_vendor/rich/_windows_renderer.py | 56 + .../site-packages/pip/_vendor/rich/_wrap.py | 56 + .../site-packages/pip/_vendor/rich/abc.py | 33 + .../site-packages/pip/_vendor/rich/align.py | 311 + .../site-packages/pip/_vendor/rich/ansi.py | 240 + .../site-packages/pip/_vendor/rich/bar.py | 94 + .../site-packages/pip/_vendor/rich/box.py | 517 + .../site-packages/pip/_vendor/rich/cells.py | 154 + .../site-packages/pip/_vendor/rich/color.py | 622 ++ .../pip/_vendor/rich/color_triplet.py | 38 + .../site-packages/pip/_vendor/rich/columns.py | 187 + .../site-packages/pip/_vendor/rich/console.py | 2633 +++++ .../pip/_vendor/rich/constrain.py | 37 + .../pip/_vendor/rich/containers.py | 167 + .../site-packages/pip/_vendor/rich/control.py | 225 + .../pip/_vendor/rich/default_styles.py | 190 + .../pip/_vendor/rich/diagnose.py | 37 + .../site-packages/pip/_vendor/rich/emoji.py | 96 + .../site-packages/pip/_vendor/rich/errors.py | 34 + .../pip/_vendor/rich/file_proxy.py | 57 + .../pip/_vendor/rich/filesize.py | 89 + .../pip/_vendor/rich/highlighter.py | 232 + .../site-packages/pip/_vendor/rich/json.py | 140 + .../site-packages/pip/_vendor/rich/jupyter.py | 101 + .../site-packages/pip/_vendor/rich/layout.py | 443 + .../site-packages/pip/_vendor/rich/live.py | 375 + .../pip/_vendor/rich/live_render.py | 113 + .../site-packages/pip/_vendor/rich/logging.py | 289 + .../site-packages/pip/_vendor/rich/markup.py | 246 + .../site-packages/pip/_vendor/rich/measure.py | 151 + .../site-packages/pip/_vendor/rich/padding.py | 141 + .../site-packages/pip/_vendor/rich/pager.py | 34 + .../site-packages/pip/_vendor/rich/palette.py | 100 + .../site-packages/pip/_vendor/rich/panel.py | 308 + .../site-packages/pip/_vendor/rich/pretty.py | 994 ++ .../pip/_vendor/rich/progress.py | 1702 ++++ .../pip/_vendor/rich/progress_bar.py | 224 + .../site-packages/pip/_vendor/rich/prompt.py | 376 + .../pip/_vendor/rich/protocol.py | 42 + .../site-packages/pip/_vendor/rich/region.py | 10 + .../site-packages/pip/_vendor/rich/repr.py | 149 + .../site-packages/pip/_vendor/rich/rule.py | 130 + .../site-packages/pip/_vendor/rich/scope.py | 86 + .../site-packages/pip/_vendor/rich/screen.py | 54 + .../site-packages/pip/_vendor/rich/segment.py | 739 ++ .../site-packages/pip/_vendor/rich/spinner.py | 137 + .../site-packages/pip/_vendor/rich/status.py | 132 + .../site-packages/pip/_vendor/rich/style.py | 796 ++ .../site-packages/pip/_vendor/rich/styled.py | 42 + .../site-packages/pip/_vendor/rich/syntax.py | 948 ++ .../site-packages/pip/_vendor/rich/table.py | 1002 ++ .../pip/_vendor/rich/terminal_theme.py | 153 + .../site-packages/pip/_vendor/rich/text.py | 1307 +++ .../site-packages/pip/_vendor/rich/theme.py | 115 + .../site-packages/pip/_vendor/rich/themes.py | 5 + .../pip/_vendor/rich/traceback.py | 756 ++ .../site-packages/pip/_vendor/rich/tree.py | 251 + .../site-packages/pip/_vendor/six.py | 998 ++ .../pip/_vendor/tenacity/__init__.py | 608 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 27105 bytes .../__pycache__/_asyncio.cpython-312.pyc | Bin 0 -> 4825 bytes .../__pycache__/_utils.cpython-312.pyc | Bin 0 -> 2334 bytes .../__pycache__/after.cpython-312.pyc | Bin 0 -> 1643 bytes .../__pycache__/before.cpython-312.pyc | Bin 0 -> 1483 bytes .../__pycache__/before_sleep.cpython-312.pyc | Bin 0 -> 2321 bytes .../tenacity/__pycache__/nap.cpython-312.pyc | Bin 0 -> 1431 bytes .../__pycache__/retry.cpython-312.pyc | Bin 0 -> 14300 bytes .../tenacity/__pycache__/stop.cpython-312.pyc | Bin 0 -> 5587 bytes .../__pycache__/tornadoweb.cpython-312.pyc | Bin 0 -> 2605 bytes .../tenacity/__pycache__/wait.cpython-312.pyc | Bin 0 -> 12432 bytes .../pip/_vendor/tenacity/_asyncio.py | 94 + .../pip/_vendor/tenacity/_utils.py | 76 + .../pip/_vendor/tenacity/after.py | 51 + .../pip/_vendor/tenacity/before.py | 46 + .../pip/_vendor/tenacity/before_sleep.py | 71 + .../site-packages/pip/_vendor/tenacity/nap.py | 43 + .../pip/_vendor/tenacity/retry.py | 272 + .../pip/_vendor/tenacity/stop.py | 103 + .../pip/_vendor/tenacity/tornadoweb.py | 59 + .../pip/_vendor/tenacity/wait.py | 228 + .../pip/_vendor/tomli/__init__.py | 11 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 403 bytes .../tomli/__pycache__/_parser.cpython-312.pyc | Bin 0 -> 26946 bytes .../tomli/__pycache__/_re.cpython-312.pyc | Bin 0 -> 3927 bytes .../tomli/__pycache__/_types.cpython-312.pyc | Bin 0 -> 385 bytes .../pip/_vendor/tomli/_parser.py | 691 ++ .../site-packages/pip/_vendor/tomli/_re.py | 107 + .../site-packages/pip/_vendor/tomli/_types.py | 10 + .../pip/_vendor/truststore/__init__.py | 13 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 637 bytes .../__pycache__/_api.cpython-312.pyc | Bin 0 -> 15816 bytes .../__pycache__/_macos.cpython-312.pyc | Bin 0 -> 16681 bytes .../__pycache__/_openssl.cpython-312.pyc | Bin 0 -> 2234 bytes .../_ssl_constants.cpython-312.pyc | Bin 0 -> 1118 bytes .../__pycache__/_windows.cpython-312.pyc | Bin 0 -> 15525 bytes .../pip/_vendor/truststore/_api.py | 302 + .../pip/_vendor/truststore/_macos.py | 501 + .../pip/_vendor/truststore/_openssl.py | 66 + .../pip/_vendor/truststore/_ssl_constants.py | 31 + .../pip/_vendor/truststore/_windows.py | 554 ++ .../pip/_vendor/typing_extensions.py | 3072 ++++++ .../pip/_vendor/urllib3/__init__.py | 102 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3424 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 16507 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 237 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 20426 bytes .../connectionpool.cpython-312.pyc | Bin 0 -> 36461 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 13512 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 10432 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 0 -> 4037 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 0 -> 20819 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 7313 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 33987 bytes .../pip/_vendor/urllib3/_collections.py | 355 + .../pip/_vendor/urllib3/_version.py | 2 + .../pip/_vendor/urllib3/connection.py | 572 ++ .../pip/_vendor/urllib3/connectionpool.py | 1137 +++ .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 217 bytes .../_appengine_environ.cpython-312.pyc | Bin 0 -> 1867 bytes .../__pycache__/appengine.cpython-312.pyc | Bin 0 -> 11583 bytes .../__pycache__/ntlmpool.cpython-312.pyc | Bin 0 -> 5738 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 0 -> 24469 bytes .../securetransport.cpython-312.pyc | Bin 0 -> 35575 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 0 -> 7530 bytes .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 234 bytes .../__pycache__/bindings.cpython-312.pyc | Bin 0 -> 17446 bytes .../__pycache__/low_level.cpython-312.pyc | Bin 0 -> 14820 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 | 518 + .../urllib3/contrib/securetransport.py | 921 ++ .../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 | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 218 bytes .../packages/__pycache__/six.cpython-312.pyc | Bin 0 -> 41338 bytes .../urllib3/packages/backports/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 228 bytes .../__pycache__/makefile.cpython-312.pyc | Bin 0 -> 1844 bytes .../weakref_finalize.cpython-312.pyc | Bin 0 -> 7350 bytes .../urllib3/packages/backports/makefile.py | 51 + .../packages/backports/weakref_finalize.py | 155 + .../pip/_vendor/urllib3/packages/six.py | 1076 +++ .../pip/_vendor/urllib3/poolmanager.py | 556 ++ .../pip/_vendor/urllib3/request.py | 191 + .../pip/_vendor/urllib3/response.py | 879 ++ .../pip/_vendor/urllib3/util/__init__.py | 49 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1165 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 4775 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 1571 bytes .../util/__pycache__/queue.cpython-312.pyc | Bin 0 -> 1371 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 0 -> 4202 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 0 -> 3008 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 0 -> 21737 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 0 -> 15122 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 0 -> 5090 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 0 -> 10791 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 0 -> 11158 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 0 -> 15814 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 0 -> 4422 bytes .../pip/_vendor/urllib3/util/connection.py | 149 + .../pip/_vendor/urllib3/util/proxy.py | 57 + .../pip/_vendor/urllib3/util/queue.py | 22 + .../pip/_vendor/urllib3/util/request.py | 137 + .../pip/_vendor/urllib3/util/response.py | 107 + .../pip/_vendor/urllib3/util/retry.py | 622 ++ .../pip/_vendor/urllib3/util/ssl_.py | 495 + .../urllib3/util/ssl_match_hostname.py | 159 + .../pip/_vendor/urllib3/util/ssltransport.py | 221 + .../pip/_vendor/urllib3/util/timeout.py | 271 + .../pip/_vendor/urllib3/util/url.py | 435 + .../pip/_vendor/urllib3/util/wait.py | 152 + .../site-packages/pip/_vendor/vendor.txt | 24 + .../pip/_vendor/webencodings/__init__.py | 342 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 12020 bytes .../__pycache__/labels.cpython-312.pyc | Bin 0 -> 7151 bytes .../__pycache__/mklabels.cpython-312.pyc | Bin 0 -> 2718 bytes .../__pycache__/tests.cpython-312.pyc | Bin 0 -> 9270 bytes .../x_user_defined.cpython-312.pyc | Bin 0 -> 3314 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.12/site-packages/pip/py.typed | 4 + .../posix_ipc-1.3.2.dist-info/INSTALLER | 1 + .../posix_ipc-1.3.2.dist-info/METADATA | 66 + .../posix_ipc-1.3.2.dist-info/RECORD | 8 + .../posix_ipc-1.3.2.dist-info/REQUESTED | 0 .../posix_ipc-1.3.2.dist-info/WHEEL | 8 + .../licenses/LICENSE | 24 + .../posix_ipc-1.3.2.dist-info/top_level.txt | 1 + .../posix_ipc.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 132968 bytes .../werkzeug-3.1.3.dist-info/INSTALLER | 1 + .../werkzeug-3.1.3.dist-info/LICENSE.txt | 28 + .../werkzeug-3.1.3.dist-info/METADATA | 99 + .../werkzeug-3.1.3.dist-info/RECORD | 116 + .../werkzeug-3.1.3.dist-info/WHEEL | 4 + .../site-packages/werkzeug/__init__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 352 bytes .../__pycache__/_internal.cpython-312.pyc | Bin 0 -> 9772 bytes .../__pycache__/_reloader.cpython-312.pyc | Bin 0 -> 20623 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 33338 bytes .../__pycache__/formparser.cpython-312.pyc | Bin 0 -> 17038 bytes .../werkzeug/__pycache__/http.cpython-312.pyc | Bin 0 -> 50263 bytes .../__pycache__/local.cpython-312.pyc | Bin 0 -> 28493 bytes .../__pycache__/security.cpython-312.pyc | Bin 0 -> 7146 bytes .../__pycache__/serving.cpython-312.pyc | Bin 0 -> 46140 bytes .../werkzeug/__pycache__/test.cpython-312.pyc | Bin 0 -> 59882 bytes .../__pycache__/testapp.cpython-312.pyc | Bin 0 -> 8903 bytes .../werkzeug/__pycache__/urls.cpython-312.pyc | Bin 0 -> 8282 bytes .../__pycache__/user_agent.cpython-312.pyc | Bin 0 -> 2165 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 28156 bytes .../werkzeug/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 25228 bytes .../site-packages/werkzeug/_internal.py | 211 + .../site-packages/werkzeug/_reloader.py | 471 + .../werkzeug/datastructures/__init__.py | 64 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2429 bytes .../__pycache__/accept.cpython-312.pyc | Bin 0 -> 15956 bytes .../__pycache__/auth.cpython-312.pyc | Bin 0 -> 14471 bytes .../__pycache__/cache_control.cpython-312.pyc | Bin 0 -> 12238 bytes .../__pycache__/csp.cpython-312.pyc | Bin 0 -> 6199 bytes .../__pycache__/etag.cpython-312.pyc | Bin 0 -> 5414 bytes .../__pycache__/file_storage.cpython-312.pyc | Bin 0 -> 8836 bytes .../__pycache__/headers.cpython-312.pyc | Bin 0 -> 30532 bytes .../__pycache__/mixins.cpython-312.pyc | Bin 0 -> 16412 bytes .../__pycache__/range.cpython-312.pyc | Bin 0 -> 10067 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 59103 bytes .../werkzeug/datastructures/accept.py | 350 + .../werkzeug/datastructures/auth.py | 317 + .../werkzeug/datastructures/cache_control.py | 273 + .../werkzeug/datastructures/csp.py | 100 + .../werkzeug/datastructures/etag.py | 106 + .../werkzeug/datastructures/file_storage.py | 209 + .../werkzeug/datastructures/headers.py | 662 ++ .../werkzeug/datastructures/mixins.py | 317 + .../werkzeug/datastructures/range.py | 214 + .../werkzeug/datastructures/structures.py | 1239 +++ .../site-packages/werkzeug/debug/__init__.py | 565 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 23488 bytes .../debug/__pycache__/console.cpython-312.pyc | Bin 0 -> 11649 bytes .../debug/__pycache__/repr.cpython-312.pyc | Bin 0 -> 13822 bytes .../debug/__pycache__/tbtools.cpython-312.pyc | Bin 0 -> 17021 bytes .../site-packages/werkzeug/debug/console.py | 219 + .../site-packages/werkzeug/debug/repr.py | 282 + .../werkzeug/debug/shared/ICON_LICENSE.md | 6 + .../werkzeug/debug/shared/console.png | Bin 0 -> 507 bytes .../werkzeug/debug/shared/debugger.js | 344 + .../werkzeug/debug/shared/less.png | Bin 0 -> 191 bytes .../werkzeug/debug/shared/more.png | Bin 0 -> 200 bytes .../werkzeug/debug/shared/style.css | 150 + .../site-packages/werkzeug/debug/tbtools.py | 450 + .../site-packages/werkzeug/exceptions.py | 894 ++ .../site-packages/werkzeug/formparser.py | 430 + .../python3.12/site-packages/werkzeug/http.py | 1405 +++ .../site-packages/werkzeug/local.py | 653 ++ .../werkzeug/middleware/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 209 bytes .../__pycache__/dispatcher.cpython-312.pyc | Bin 0 -> 3327 bytes .../__pycache__/http_proxy.cpython-312.pyc | Bin 0 -> 9419 bytes .../__pycache__/lint.cpython-312.pyc | Bin 0 -> 17789 bytes .../__pycache__/profiler.cpython-312.pyc | Bin 0 -> 7213 bytes .../__pycache__/proxy_fix.cpython-312.pyc | Bin 0 -> 7210 bytes .../__pycache__/shared_data.cpython-312.pyc | Bin 0 -> 12765 bytes .../werkzeug/middleware/dispatcher.py | 81 + .../werkzeug/middleware/http_proxy.py | 236 + .../site-packages/werkzeug/middleware/lint.py | 439 + .../werkzeug/middleware/profiler.py | 155 + .../werkzeug/middleware/proxy_fix.py | 183 + .../werkzeug/middleware/shared_data.py | 283 + .../site-packages/werkzeug/py.typed | 0 .../werkzeug/routing/__init__.py | 134 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4682 bytes .../__pycache__/converters.cpython-312.pyc | Bin 0 -> 10922 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7925 bytes .../routing/__pycache__/map.cpython-312.pyc | Bin 0 -> 39850 bytes .../__pycache__/matcher.cpython-312.pyc | Bin 0 -> 8293 bytes .../routing/__pycache__/rules.cpython-312.pyc | Bin 0 -> 39184 bytes .../werkzeug/routing/converters.py | 261 + .../werkzeug/routing/exceptions.py | 152 + .../site-packages/werkzeug/routing/map.py | 951 ++ .../site-packages/werkzeug/routing/matcher.py | 202 + .../site-packages/werkzeug/routing/rules.py | 928 ++ .../site-packages/werkzeug/sansio/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 205 bytes .../sansio/__pycache__/http.cpython-312.pyc | Bin 0 -> 5656 bytes .../__pycache__/multipart.cpython-312.pyc | Bin 0 -> 14064 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 21898 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 31753 bytes .../sansio/__pycache__/utils.cpython-312.pyc | Bin 0 -> 6195 bytes .../site-packages/werkzeug/sansio/http.py | 170 + .../werkzeug/sansio/multipart.py | 323 + .../site-packages/werkzeug/sansio/request.py | 534 + .../site-packages/werkzeug/sansio/response.py | 763 ++ .../site-packages/werkzeug/sansio/utils.py | 167 + .../site-packages/werkzeug/security.py | 166 + .../site-packages/werkzeug/serving.py | 1125 +++ .../python3.12/site-packages/werkzeug/test.py | 1464 +++ .../site-packages/werkzeug/testapp.py | 194 + .../python3.12/site-packages/werkzeug/urls.py | 203 + .../site-packages/werkzeug/user_agent.py | 47 + .../site-packages/werkzeug/utils.py | 691 ++ .../werkzeug/wrappers/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 329 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 26159 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 34584 bytes .../werkzeug/wrappers/request.py | 650 ++ .../werkzeug/wrappers/response.py | 831 ++ .../python3.12/site-packages/werkzeug/wsgi.py | 595 ++ web_viewer/.venv/lib64 | 1 + web_viewer/.venv/pyvenv.cfg | 5 + web_viewer/README.md | 136 + .../shared_memory_reader.cpython-312.pyc | Bin 0 -> 8247 bytes web_viewer/app.py | 169 + web_viewer/requirements.txt | 2 + web_viewer/shared_memory_reader.py | 199 + web_viewer/start.sh | 54 + web_viewer/templates/index.html | 382 + web_viewer/test_shm.py | 74 + 1353 files changed, 253533 insertions(+) create mode 100644 README.md create mode 160000 SkyWatcher create mode 160000 beacon_track create mode 100755 build_and_run.sh create mode 100644 web_viewer/.venv/bin/Activate.ps1 create mode 100644 web_viewer/.venv/bin/activate create mode 100644 web_viewer/.venv/bin/activate.csh create mode 100644 web_viewer/.venv/bin/activate.fish create mode 100755 web_viewer/.venv/bin/flask create mode 100755 web_viewer/.venv/bin/pip create mode 100755 web_viewer/.venv/bin/pip3 create mode 100755 web_viewer/.venv/bin/pip3.12 create mode 120000 web_viewer/.venv/bin/python create mode 120000 web_viewer/.venv/bin/python3 create mode 120000 web_viewer/.venv/bin/python3.12 create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker/__pycache__/_utilities.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker/__pycache__/base.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker/_utilities.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker/base.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/blinker/py.typed create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/licenses/LICENSE.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/_compat.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/_termui_impl.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/_textwrap.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/_utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/_winconsole.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/core.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/decorators.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/formatting.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/globals.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/parser.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/py.typed create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/shell_completion.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/termui.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/testing.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/types.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/click/utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/REQUESTED create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/entry_points.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/licenses/LICENSE.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__main__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/__main__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/app.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/blueprints.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/config.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/ctx.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/globals.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/helpers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/signals.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/templating.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/wrappers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/app.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/blueprints.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/cli.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/config.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/ctx.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/debughelpers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/globals.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/helpers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/json/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/json/__pycache__/provider.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/json/provider.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/json/tag.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/logging.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/py.typed create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/README.md create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/app.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/blueprints.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/scaffold.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/sessions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/signals.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/templating.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/testing.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/typing.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/views.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/flask/wrappers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/_json.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/exc.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/serializer.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/timed.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/url_safe.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/_json.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/encoding.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/exc.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/py.typed create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/serializer.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/signer.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/timed.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/url_safe.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/entry_points.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/filters.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/idtracking.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/loaders.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/meta.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/nodes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/runtime.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/_identifier.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/async_utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/bccache.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/compiler.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/constants.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/debug.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/defaults.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/environment.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/ext.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/filters.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/idtracking.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/lexer.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/loaders.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/meta.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/nativetypes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/nodes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/optimizer.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/parser.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/py.typed create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/runtime.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/sandbox.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/tests.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/jinja2/visitor.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/licenses/LICENSE.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/top_level.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe/__pycache__/_native.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe/_native.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe/_speedups.c create mode 100755 web_viewer/.venv/lib/python3.12/site-packages/markupsafe/_speedups.cpython-312-x86_64-linux-gnu.so create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe/_speedups.pyi create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/markupsafe/py.typed create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/AUTHORS.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/LICENSE.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/REQUESTED create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/entry_points.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip-24.0.dist-info/top_level.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/__main__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/__pip-runner__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/main.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/build_env.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cache.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/base_command.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/command_context.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/main.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/main_parser.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/parser.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/req_command.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/spinners.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/cli/status_codes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/cache.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/check.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/completion.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/debug.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/download.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/hash.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/help.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/index.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/search.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/cache.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/check.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/completion.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/configuration.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/debug.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/download.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/freeze.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/hash.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/help.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/index.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/inspect.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/install.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/list.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/search.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/show.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/uninstall.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/commands/wheel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/configuration.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/base.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/installed.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/sdist.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/distributions/wheel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/index/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/index/collector.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/index/package_finder.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/index/sources.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/locations/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/locations/_distutils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/locations/_sysconfig.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/locations/base.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/main.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/_json.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/base.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_compat.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_dists.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_envs.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/metadata/pkg_resources.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/candidate.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/direct_url.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/format_control.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/index.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/installation_report.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/link.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/scheme.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/search_scope.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/target_python.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/models/wheel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/auth.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/cache.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/download.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/lazy_wheel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/session.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/network/xmlrpc.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/build_tracker.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata_editable.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/build_tracker.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata_editable.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata_legacy.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel_editable.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel_legacy.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/check.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/freeze.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/install/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/editable_legacy.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/install/editable_legacy.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/install/wheel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/operations/prepare.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/pyproject.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/constructors.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/req_file.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/req_install.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/req_set.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/base.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/resolver.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/base.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/factory.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/provider.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/reporter.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/requirements.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/resolver.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/self_outdated_check.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/models.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/_jaraco_text.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/_log.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/appdirs.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/compat.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/compatibility_tags.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/datetime.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/deprecation.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/direct_url_helpers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/egg_link.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/encoding.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/entrypoints.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/filesystem.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/filetypes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/glibc.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/hashes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/logging.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/misc.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/models.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/packaging.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/subprocess.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/unpacking.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/urls.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/utils/wheel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/git.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/subversion.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_internal/wheel_builder.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/six.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/typing_extensions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/certifi/core.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/big5freq.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/big5prober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/chardistribution.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/charsetgroupprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/charsetprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/codingstatemachine.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/codingstatemachinedict.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/cp949prober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/enums.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/escprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/escsm.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/eucjpprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/euckrfreq.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/euckrprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/euctwfreq.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/euctwprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/gb2312freq.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/gb2312prober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/hebrewprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/jisfreq.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/johabfreq.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/johabprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/jpcntx.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langbulgarianmodel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langgreekmodel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langhebrewmodel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langhungarianmodel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langrussianmodel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langthaimodel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/langturkishmodel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/latin1prober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/macromanprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/mbcharsetprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/mbcsgroupprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/mbcssm.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/resultdict.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/sbcharsetprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/sbcsgroupprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/sjisprober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/universaldetector.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/utf1632prober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/utf8prober.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/__pycache__/version.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/big5freq.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/big5prober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/chardistribution.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/charsetgroupprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/charsetprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/__pycache__/chardetect.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/cli/chardetect.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/codingstatemachine.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/codingstatemachinedict.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/cp949prober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/enums.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/escprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/escsm.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/eucjpprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/euckrfreq.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/euckrprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/euctwfreq.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/euctwprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/gb2312freq.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/gb2312prober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/hebrewprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/jisfreq.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/johabfreq.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/johabprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/jpcntx.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/langbulgarianmodel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/langgreekmodel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/langhebrewmodel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/langhungarianmodel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/langrussianmodel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/langthaimodel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/langturkishmodel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/latin1prober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/macromanprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcharsetprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcsgroupprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/mbcssm.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/__pycache__/languages.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/metadata/languages.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/resultdict.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/sbcharsetprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/sbcsgroupprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/sjisprober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/universaldetector.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/utf1632prober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/utf8prober.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/chardet/version.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/ansi.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/ansitowin32.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/initialise.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/win32.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/__pycache__/winterm.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/ansi.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/ansitowin32.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/initialise.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/ansi_test.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/initialise_test.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/isatty_test.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/__pycache__/winterm_test.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/ansi_test.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/ansitowin32_test.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/initialise_test.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/isatty_test.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/tests/winterm_test.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/win32.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/colorama/winterm.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/locators.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/manifest.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/metadata.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/compat.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/database.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/index.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/locators.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/markers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/resources.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/util.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/version.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distro/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distro/__main__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/distro/distro.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/codec.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/compat.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/core.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/intranges.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/package_data.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack/ext.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__about__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__about__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/_manylinux.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/_musllinux.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/markers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/tags.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/packaging/version.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__main__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/android.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/api.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/macos.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/unix.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/version.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/windows.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__main__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/cmdline.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/console.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/filter.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatter.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal256.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexer.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/_mapping.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/python.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/modeline.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/plugin.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/regexopt.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/scanner.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/sphinxext.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/style.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/token.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/unistring.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pygments/util.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/actions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/common.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/core.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/helpers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/results.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/testing.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/unicode.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/__pycache__/util.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/actions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/common.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/core.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/diagram/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/diagram/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_compat.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/help.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/api.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/help.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/models.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/resolvers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/json.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/status.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/align.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/box.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/color.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/console.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/control.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/json.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/jupyter.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/layout.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/live.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/live_render.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/logging.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/markup.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/measure.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/padding.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/pager.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/palette.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/panel.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/pretty.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/progress.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/progress_bar.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/prompt.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/protocol.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/region.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/repr.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/rule.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/scope.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/screen.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/segment.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/spinner.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/status.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/style.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/styled.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/syntax.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/table.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/terminal_theme.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/text.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/theme.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/themes.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/traceback.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/rich/tree.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/six.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/_asyncio.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/_utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/after.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/before.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/before_sleep.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/nap.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/retry.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/stop.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/tornadoweb.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/__pycache__/wait.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/_asyncio.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/_utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/after.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/before.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/before_sleep.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/nap.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/retry.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/stop.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/tornadoweb.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tenacity/wait.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tomli/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tomli/_parser.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tomli/_re.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/tomli/_types.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/_api.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/_macos.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/_openssl.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/_ssl_constants.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/truststore/_windows.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/typing_extensions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/_version.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/weakref_finalize.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/request.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/response.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/proxy.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssltransport.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/vendor.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/labels.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/mklabels.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/tests.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/__pycache__/x_user_defined.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/labels.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/mklabels.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/tests.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/_vendor/webencodings/x_user_defined.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/pip/py.typed create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/posix_ipc-1.3.2.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/posix_ipc-1.3.2.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/posix_ipc-1.3.2.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/posix_ipc-1.3.2.dist-info/REQUESTED create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/posix_ipc-1.3.2.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/posix_ipc-1.3.2.dist-info/licenses/LICENSE create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/posix_ipc-1.3.2.dist-info/top_level.txt create mode 100755 web_viewer/.venv/lib/python3.12/site-packages/posix_ipc.cpython-312-x86_64-linux-gnu.so create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/INSTALLER create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/LICENSE.txt create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/METADATA create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/RECORD create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug-3.1.3.dist-info/WHEEL create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/_internal.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/_reloader.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/formparser.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/http.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/local.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/security.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/serving.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/test.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/testapp.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/urls.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/user_agent.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/__pycache__/wsgi.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/_internal.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/_reloader.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/accept.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/cache_control.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/headers.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/range.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/__pycache__/structures.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/accept.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/auth.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/cache_control.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/csp.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/etag.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/file_storage.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/headers.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/mixins.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/range.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/datastructures/structures.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/__pycache__/console.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/__pycache__/repr.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/console.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/repr.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/shared/ICON_LICENSE.md create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/shared/console.png create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/shared/debugger.js create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/shared/less.png create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/shared/more.png create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/shared/style.css create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/debug/tbtools.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/formparser.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/http.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/local.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/lint.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/dispatcher.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/http_proxy.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/lint.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/profiler.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/proxy_fix.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/middleware/shared_data.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/py.typed create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/converters.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/map.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/matcher.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/__pycache__/rules.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/converters.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/exceptions.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/map.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/matcher.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/routing/rules.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/http.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/request.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/response.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/__pycache__/utils.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/http.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/multipart.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/request.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/response.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/sansio/utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/security.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/serving.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/test.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/testapp.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/urls.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/user_agent.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/utils.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/wrappers/__init__.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/wrappers/__pycache__/request.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/wrappers/__pycache__/response.cpython-312.pyc create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/wrappers/request.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/wrappers/response.py create mode 100644 web_viewer/.venv/lib/python3.12/site-packages/werkzeug/wsgi.py create mode 120000 web_viewer/.venv/lib64 create mode 100644 web_viewer/.venv/pyvenv.cfg create mode 100644 web_viewer/README.md create mode 100644 web_viewer/__pycache__/shared_memory_reader.cpython-312.pyc create mode 100644 web_viewer/app.py create mode 100644 web_viewer/requirements.txt create mode 100644 web_viewer/shared_memory_reader.py create mode 100755 web_viewer/start.sh create mode 100644 web_viewer/templates/index.html create mode 100755 web_viewer/test_shm.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e75a8a --- /dev/null +++ b/README.md @@ -0,0 +1,211 @@ +# Beacon Tracker с Web Viewer + +Проект для отслеживания белой точки (beacon) с камеры и отображения видео потока в реальном времени через веб-интерфейс. + +## Структура проекта + +``` +radar_frontend/ +├── beacon_track/ # C++ приложение для захвата и обработки видео +│ ├── src/ # Исходный код +│ ├── include/ # Заголовочные файлы +│ ├── build/ # Сборка проекта +│ └── config.ini # Конфигурация +│ +├── web_viewer/ # Веб-приложение для просмотра видео +│ ├── app.py # Flask сервер +│ ├── shared_memory_reader.py # Модуль для чтения из shared memory +│ ├── templates/ # HTML шаблоны +│ └── start.sh # Скрипт запуска +│ +└── build_and_run.sh # Скрипт сборки и запуска +``` + +## Быстрый старт + +### 1. Сборка C++ проекта + +```bash +cd /home/awe/Documents/radar_frontend +./build_and_run.sh +``` + +Этот скрипт: +- Соберет C++ проект beacon_track +- Покажет инструкции по запуску +- Опционально запустит beacon tracker + +### 2. Установка зависимостей Python + +```bash +cd web_viewer +pip install -r requirements.txt +``` + +### 3. Запуск + +**Терминал 1 - Beacon Tracker:** +```bash +cd beacon_track/build +./main realtime output.txt +``` + +**Терминал 2 - Web Viewer:** +```bash +cd web_viewer +./start.sh +``` + +**Браузер:** +``` +http://localhost:5000 +``` + +## Как это работает + +### Архитектура + +1. **C++ Beacon Tracker** (`beacon_track/`) + - Захватывает видео с камеры через GStreamer + - Детектирует белую точку на кадрах + - Кодирует кадры в JPEG + - Записывает JPEG кадры в shared memory (`BeaconFrameBuffer`) + +2. **Python Flask Server** (`web_viewer/app.py`) + - Читает JPEG кадры из shared memory + - Стримит их через Server-Sent Events (SSE) + - Предоставляет REST API для статуса + +3. **Web Frontend** (`web_viewer/templates/index.html`) + - Подключается к SSE потоку + - Декодирует base64 JPEG кадры + - Отображает видео в реальном времени + - Показывает метрики (FPS, битрейт, задержка) + +### Shared Memory + +Проект использует POSIX shared memory для передачи кадров между C++ и Python: + +- **Имя сегмента**: `BeaconFrameBuffer` +- **Структура**: SharedFrameBufferHeader + FrameHeader + JPEG данные +- **Синхронизация**: seqlock-подобный протокол с атомарными операциями +- **Размер**: настраивается через `FrameBufferMaxSize` в config.ini + +## Конфигурация + +### Основные параметры в `beacon_track/config.ini` + +```ini +[Processing] +# Включить запись кадров в shared memory +EnableFrameBuffer = true + +# Максимальный размер JPEG кадра (в байтах) +FrameBufferMaxSize = 2097152 # 2 MB + +# Качество JPEG сжатия (0-100) +FrameBufferJpegQuality = 85 + +[InputSettings] +# Разрешение видео +Width = 1920 +Height = 1080 + +# Формат и FPS +Format = YUY2 +FrameRate = 1/1 +``` + +## Возможности Web Viewer + +- ✅ Просмотр видео в реальном времени +- ✅ Отображение FPS, битрейта, задержки +- ✅ Информация о разрешении и размере кадров +- ✅ Автоматическое переподключение при потере связи +- ✅ Скриншоты (кнопка "Скриншот") +- ✅ Адаптивный дизайн +- ✅ Темная/светлая тема + +## Системные требования + +### Для C++ приложения: +- Ubuntu/Debian Linux +- GCC/Clang с поддержкой C++17 +- CMake >= 3.10 +- OpenCV 4.x +- Boost (для Boost.Interprocess) +- GStreamer (для захвата с камеры) + +### Для Web Viewer: +- Python 3.7+ +- Flask >= 3.0.0 +- posix_ipc >= 1.1.0 + +## Производительность + +- **Разрешение**: до 1920x1080 (зависит от камеры) +- **FPS**: до 30-60 FPS +- **Задержка**: 20-100ms +- **Битрейт**: 1-5 Mbps (зависит от качества JPEG) +- **CPU**: ~20-40% на одно ядро + +## Troubleshooting + +### Shared memory не найден + +``` +ERROR: Shared memory segment 'BeaconFrameBuffer' does not exist +``` + +**Решение:** +1. Убедитесь, что beacon_track запущен +2. Проверьте `EnableFrameBuffer = true` в config.ini +3. Пересоберите проект после изменений + +### Нет кадров в браузере + +**Решение:** +1. Проверьте логи C++ приложения на ошибки +2. Убедитесь, что камера работает +3. Проверьте размер JPEG кадров < FrameBufferMaxSize + +### Низкий FPS + +**Решение:** +1. Уменьшите качество JPEG (`FrameBufferJpegQuality`) +2. Уменьшите разрешение камеры +3. Проверьте загрузку CPU + +### Python не может импортировать posix_ipc + +```bash +pip install posix_ipc +``` + +Если не помогает: +```bash +sudo apt-get install python3-dev +pip install --upgrade posix_ipc +``` + +## API Endpoints + +Web Viewer предоставляет следующие endpoints: + +- `GET /` - главная страница +- `GET /stream` - SSE поток с кадрами +- `GET /status` - JSON статус +- `GET /latest_frame` - последний кадр (JPEG) + +## Дополнительные материалы + +- **Beacon Tracker README**: `beacon_track/README.md` +- **Web Viewer README**: `web_viewer/README.md` + +## Лицензия + +MIT License + +## Автор + +Проект создан для отслеживания beacon в реальном времени diff --git a/SkyWatcher b/SkyWatcher new file mode 160000 index 0000000..173d0fe --- /dev/null +++ b/SkyWatcher @@ -0,0 +1 @@ +Subproject commit 173d0fe4d55d94677e81b451b9c749648fc50ca2 diff --git a/beacon_track b/beacon_track new file mode 160000 index 0000000..03be2bc --- /dev/null +++ b/beacon_track @@ -0,0 +1 @@ +Subproject commit 03be2bc2b967883d0c14453ca1d44bb77bc54559 diff --git a/build_and_run.sh b/build_and_run.sh new file mode 100755 index 0000000..37bf570 --- /dev/null +++ b/build_and_run.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Build and Run script for Beacon Tracker with Web Viewer +# This script builds the C++ project and provides instructions to run + +echo "===================================" +echo "Beacon Tracker - Build & Run" +echo "===================================" +echo "" + +# Navigate to beacon_track directory +cd "$(dirname "$0")/beacon_track/build" || exit 1 + +echo "Building C++ project..." +echo "" + +# Run CMake if needed +if [ ! -f "Makefile" ]; then + echo "Running CMake..." + cmake .. || { echo "ERROR: CMake failed"; exit 1; } +fi + +# Build the project +echo "Compiling..." +make || { echo "ERROR: Make failed"; exit 1; } + +echo "" +echo "✓ Build successful!" +echo "" +echo "===================================" +echo "How to run:" +echo "===================================" +echo "" +echo "1. Start the beacon tracker (this terminal):" +echo " cd $(pwd)" +echo " ./main realtime output.txt" +echo "" +echo "2. Start the web viewer (new terminal):" +echo " cd $(dirname "$0")/web_viewer" +echo " ./start.sh" +echo "" +echo "3. Open browser:" +echo " http://localhost:5000" +echo "" +echo "===================================" +echo "" + +read -p "Start beacon tracker now? (y/N) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "" + echo "Starting beacon tracker..." + echo "Press Ctrl+C to stop" + echo "" + ./main realtime output.txt +fi diff --git a/web_viewer/.venv/bin/Activate.ps1 b/web_viewer/.venv/bin/Activate.ps1 new file mode 100644 index 0000000..eeea358 --- /dev/null +++ b/web_viewer/.venv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/web_viewer/.venv/bin/activate b/web_viewer/.venv/bin/activate new file mode 100644 index 0000000..9c9d6b9 --- /dev/null +++ b/web_viewer/.venv/bin/activate @@ -0,0 +1,70 @@ +# 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 + + # Call hash to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + hash -r 2> /dev/null + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +# on Windows, a path can contain colons and backslashes and has to be converted: +if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + export VIRTUAL_ENV=$(cygpath /home/awe/Documents/radar_frontend/web_viewer/.venv) +else + # use the path as-is + export VIRTUAL_ENV=/home/awe/Documents/radar_frontend/web_viewer/.venv +fi + +_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:-}" + PS1='(.venv) '"${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT='(.venv) ' + export VIRTUAL_ENV_PROMPT +fi + +# Call hash to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +hash -r 2> /dev/null diff --git a/web_viewer/.venv/bin/activate.csh b/web_viewer/.venv/bin/activate.csh new file mode 100644 index 0000000..c007bcb --- /dev/null +++ b/web_viewer/.venv/bin/activate.csh @@ -0,0 +1,27 @@ +# 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; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV /home/awe/Documents/radar_frontend/web_viewer/.venv + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/"bin":$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = '(.venv) '"$prompt" + setenv VIRTUAL_ENV_PROMPT '(.venv) ' +endif + +alias pydoc python -m pydoc + +rehash diff --git a/web_viewer/.venv/bin/activate.fish b/web_viewer/.venv/bin/activate.fish new file mode 100644 index 0000000..66cd18a --- /dev/null +++ b/web_viewer/.venv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/). You cannot run it directly. + +function deactivate -d "Exit virtual environment 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" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV /home/awe/Documents/radar_frontend/web_viewer/.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 + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) '(.venv) ' (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT '(.venv) ' +end diff --git a/web_viewer/.venv/bin/flask b/web_viewer/.venv/bin/flask new file mode 100755 index 0000000..ce81e81 --- /dev/null +++ b/web_viewer/.venv/bin/flask @@ -0,0 +1,8 @@ +#!/home/awe/Documents/radar_frontend/web_viewer/.venv/bin/python3.12 +# -*- 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/web_viewer/.venv/bin/pip b/web_viewer/.venv/bin/pip new file mode 100755 index 0000000..62676d3 --- /dev/null +++ b/web_viewer/.venv/bin/pip @@ -0,0 +1,8 @@ +#!/home/awe/Documents/radar_frontend/web_viewer/.venv/bin/python3.12 +# -*- 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/web_viewer/.venv/bin/pip3 b/web_viewer/.venv/bin/pip3 new file mode 100755 index 0000000..62676d3 --- /dev/null +++ b/web_viewer/.venv/bin/pip3 @@ -0,0 +1,8 @@ +#!/home/awe/Documents/radar_frontend/web_viewer/.venv/bin/python3.12 +# -*- 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/web_viewer/.venv/bin/pip3.12 b/web_viewer/.venv/bin/pip3.12 new file mode 100755 index 0000000..62676d3 --- /dev/null +++ b/web_viewer/.venv/bin/pip3.12 @@ -0,0 +1,8 @@ +#!/home/awe/Documents/radar_frontend/web_viewer/.venv/bin/python3.12 +# -*- 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/web_viewer/.venv/bin/python b/web_viewer/.venv/bin/python new file mode 120000 index 0000000..11b9d88 --- /dev/null +++ b/web_viewer/.venv/bin/python @@ -0,0 +1 @@ +python3.12 \ No newline at end of file diff --git a/web_viewer/.venv/bin/python3 b/web_viewer/.venv/bin/python3 new file mode 120000 index 0000000..11b9d88 --- /dev/null +++ b/web_viewer/.venv/bin/python3 @@ -0,0 +1 @@ +python3.12 \ No newline at end of file diff --git a/web_viewer/.venv/bin/python3.12 b/web_viewer/.venv/bin/python3.12 new file mode 120000 index 0000000..c7c6ec2 --- /dev/null +++ b/web_viewer/.venv/bin/python3.12 @@ -0,0 +1 @@ +/bin/python3.12 \ No newline at end of file diff --git a/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt new file mode 100644 index 0000000..79c9825 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright 2010 Jason Kirtland + +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/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA new file mode 100644 index 0000000..6d343f5 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/METADATA @@ -0,0 +1,60 @@ +Metadata-Version: 2.3 +Name: blinker +Version: 1.9.0 +Summary: Fast, simple object-to-object and broadcast signaling +Author: Jason Kirtland +Maintainer-email: Pallets Ecosystem +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://blinker.readthedocs.io +Project-URL: Source, https://github.com/pallets-eco/blinker/ + +# Blinker + +Blinker provides a fast dispatching system that allows any number of +interested parties to subscribe to events, or "signals". + + +## Pallets Community Ecosystem + +> [!IMPORTANT]\ +> This project is part of the Pallets Community Ecosystem. Pallets is the open +> source organization that maintains Flask; Pallets-Eco enables community +> maintenance of related projects. If you are interested in helping maintain +> this project, please reach out on [the Pallets Discord server][discord]. +> +> [discord]: https://discord.gg/pallets + + +## Example + +Signal receivers can subscribe to specific senders or receive signals +sent by any sender. + +```pycon +>>> from blinker import signal +>>> started = signal('round-started') +>>> def each(round): +... print(f"Round {round}") +... +>>> started.connect(each) + +>>> def round_two(round): +... print("This is round two.") +... +>>> started.connect(round_two, sender=2) + +>>> for round in range(1, 4): +... started.send(round) +... +Round 1! +Round 2! +This is round two. +Round 3! +``` + diff --git a/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD new file mode 100644 index 0000000..d4f985b --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/RECORD @@ -0,0 +1,12 @@ +blinker-1.9.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +blinker-1.9.0.dist-info/LICENSE.txt,sha256=nrc6HzhZekqhcCXSrhvjg5Ykx5XphdTw6Xac4p-spGc,1054 +blinker-1.9.0.dist-info/METADATA,sha256=uIRiM8wjjbHkCtbCyTvctU37IAZk0kEe5kxAld1dvzA,1633 +blinker-1.9.0.dist-info/RECORD,, +blinker-1.9.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82 +blinker/__init__.py,sha256=I2EdZqpy4LyjX17Hn1yzJGWCjeLaVaPzsMgHkLfj_cQ,317 +blinker/__pycache__/__init__.cpython-312.pyc,, +blinker/__pycache__/_utilities.cpython-312.pyc,, +blinker/__pycache__/base.cpython-312.pyc,, +blinker/_utilities.py,sha256=0J7eeXXTUx0Ivf8asfpx0ycVkp0Eqfqnj117x2mYX9E,1675 +blinker/base.py,sha256=QpDuvXXcwJF49lUBcH5BiST46Rz9wSG7VW_p7N_027M,19132 +blinker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL new file mode 100644 index 0000000..e3c6fee --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/blinker-1.9.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.10.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/web_viewer/.venv/lib/python3.12/site-packages/blinker/__init__.py b/web_viewer/.venv/lib/python3.12/site-packages/blinker/__init__.py new file mode 100644 index 0000000..1772fa4 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/blinker/__init__.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from .base import ANY +from .base import default_namespace +from .base import NamedSignal +from .base import Namespace +from .base import Signal +from .base import signal + +__all__ = [ + "ANY", + "default_namespace", + "NamedSignal", + "Namespace", + "Signal", + "signal", +] diff --git a/web_viewer/.venv/lib/python3.12/site-packages/blinker/__pycache__/__init__.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/blinker/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95e5c998ef62333713e6345acfebaec98bbc2a4c GIT binary patch literal 509 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 Pw>> Symbol('foo') is Symbol('foo') + True + >>> Symbol('foo') + foo + """ + + symbols: t.ClassVar[dict[str, Symbol]] = {} + + def __new__(cls, name: str) -> Symbol: + if name in cls.symbols: + return cls.symbols[name] + + obj = super().__new__(cls) + cls.symbols[name] = obj + return obj + + def __init__(self, name: str) -> None: + self.name = name + + def __repr__(self) -> str: + return self.name + + def __getnewargs__(self) -> tuple[t.Any, ...]: + return (self.name,) + + +def make_id(obj: object) -> c.Hashable: + """Get a stable identifier for a receiver or sender, to be used as a dict + key or in a set. + """ + if inspect.ismethod(obj): + # The id of a bound method is not stable, but the id of the unbound + # function and instance are. + return id(obj.__func__), id(obj.__self__) + + if isinstance(obj, (str, int)): + # Instances with the same value always compare equal and have the same + # hash, even if the id may change. + return obj + + # Assume other types are not hashable but will always be the same instance. + return id(obj) + + +def make_ref(obj: T, callback: c.Callable[[ref[T]], None] | None = None) -> ref[T]: + if inspect.ismethod(obj): + return WeakMethod(obj, callback) # type: ignore[arg-type, return-value] + + return ref(obj, callback) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/blinker/base.py b/web_viewer/.venv/lib/python3.12/site-packages/blinker/base.py new file mode 100644 index 0000000..d051b94 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/blinker/base.py @@ -0,0 +1,512 @@ +from __future__ import annotations + +import collections.abc as c +import sys +import typing as t +import weakref +from collections import defaultdict +from contextlib import contextmanager +from functools import cached_property +from inspect import iscoroutinefunction + +from ._utilities import make_id +from ._utilities import make_ref +from ._utilities import Symbol + +F = t.TypeVar("F", bound=c.Callable[..., t.Any]) + +ANY = Symbol("ANY") +"""Symbol for "any sender".""" + +ANY_ID = 0 + + +class Signal: + """A notification emitter. + + :param doc: The docstring for the signal. + """ + + ANY = ANY + """An alias for the :data:`~blinker.ANY` sender symbol.""" + + set_class: type[set[t.Any]] = set + """The set class to use for tracking connected receivers and senders. + Python's ``set`` is unordered. If receivers must be dispatched in the order + they were connected, an ordered set implementation can be used. + + .. versionadded:: 1.7 + """ + + @cached_property + def receiver_connected(self) -> Signal: + """Emitted at the end of each :meth:`connect` call. + + The signal sender is the signal instance, and the :meth:`connect` + arguments are passed through: ``receiver``, ``sender``, and ``weak``. + + .. versionadded:: 1.2 + """ + return Signal(doc="Emitted after a receiver connects.") + + @cached_property + def receiver_disconnected(self) -> Signal: + """Emitted at the end of each :meth:`disconnect` call. + + The sender is the signal instance, and the :meth:`disconnect` arguments + are passed through: ``receiver`` and ``sender``. + + This signal is emitted **only** when :meth:`disconnect` is called + explicitly. This signal cannot be emitted by an automatic disconnect + when a weakly referenced receiver or sender goes out of scope, as the + instance is no longer be available to be used as the sender for this + signal. + + An alternative approach is available by subscribing to + :attr:`receiver_connected` and setting up a custom weakref cleanup + callback on weak receivers and senders. + + .. versionadded:: 1.2 + """ + return Signal(doc="Emitted after a receiver disconnects.") + + def __init__(self, doc: str | None = None) -> None: + if doc: + self.__doc__ = doc + + self.receivers: dict[ + t.Any, weakref.ref[c.Callable[..., t.Any]] | c.Callable[..., t.Any] + ] = {} + """The map of connected receivers. Useful to quickly check if any + receivers are connected to the signal: ``if s.receivers:``. The + structure and data is not part of the public API, but checking its + boolean value is. + """ + + self.is_muted: bool = False + self._by_receiver: dict[t.Any, set[t.Any]] = defaultdict(self.set_class) + self._by_sender: dict[t.Any, set[t.Any]] = defaultdict(self.set_class) + self._weak_senders: dict[t.Any, weakref.ref[t.Any]] = {} + + def connect(self, receiver: F, sender: t.Any = ANY, weak: bool = True) -> F: + """Connect ``receiver`` to be called when the signal is sent by + ``sender``. + + :param receiver: The callable to call when :meth:`send` is called with + the given ``sender``, passing ``sender`` as a positional argument + along with any extra keyword arguments. + :param sender: Any object or :data:`ANY`. ``receiver`` will only be + called when :meth:`send` is called with this sender. If ``ANY``, the + receiver will be called for any sender. A receiver may be connected + to multiple senders by calling :meth:`connect` multiple times. + :param weak: Track the receiver with a :mod:`weakref`. The receiver will + be automatically disconnected when it is garbage collected. When + connecting a receiver defined within a function, set to ``False``, + otherwise it will be disconnected when the function scope ends. + """ + receiver_id = make_id(receiver) + sender_id = ANY_ID if sender is ANY else make_id(sender) + + if weak: + self.receivers[receiver_id] = make_ref( + receiver, self._make_cleanup_receiver(receiver_id) + ) + else: + self.receivers[receiver_id] = receiver + + self._by_sender[sender_id].add(receiver_id) + self._by_receiver[receiver_id].add(sender_id) + + if sender is not ANY and sender_id not in self._weak_senders: + # store a cleanup for weakref-able senders + try: + self._weak_senders[sender_id] = make_ref( + sender, self._make_cleanup_sender(sender_id) + ) + except TypeError: + pass + + if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers: + try: + self.receiver_connected.send( + self, receiver=receiver, sender=sender, weak=weak + ) + except TypeError: + # TODO no explanation or test for this + self.disconnect(receiver, sender) + raise + + return receiver + + def connect_via(self, sender: t.Any, weak: bool = False) -> c.Callable[[F], F]: + """Connect the decorated function to be called when the signal is sent + by ``sender``. + + The decorated function will be called when :meth:`send` is called with + the given ``sender``, passing ``sender`` as a positional argument along + with any extra keyword arguments. + + :param sender: Any object or :data:`ANY`. ``receiver`` will only be + called when :meth:`send` is called with this sender. If ``ANY``, the + receiver will be called for any sender. A receiver may be connected + to multiple senders by calling :meth:`connect` multiple times. + :param weak: Track the receiver with a :mod:`weakref`. The receiver will + be automatically disconnected when it is garbage collected. When + connecting a receiver defined within a function, set to ``False``, + otherwise it will be disconnected when the function scope ends.= + + .. versionadded:: 1.1 + """ + + def decorator(fn: F) -> F: + self.connect(fn, sender, weak) + return fn + + return decorator + + @contextmanager + def connected_to( + self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY + ) -> c.Generator[None, None, None]: + """A context manager that temporarily connects ``receiver`` to the + signal while a ``with`` block executes. When the block exits, the + receiver is disconnected. Useful for tests. + + :param receiver: The callable to call when :meth:`send` is called with + the given ``sender``, passing ``sender`` as a positional argument + along with any extra keyword arguments. + :param sender: Any object or :data:`ANY`. ``receiver`` will only be + called when :meth:`send` is called with this sender. If ``ANY``, the + receiver will be called for any sender. + + .. versionadded:: 1.1 + """ + self.connect(receiver, sender=sender, weak=False) + + try: + yield None + finally: + self.disconnect(receiver) + + @contextmanager + def muted(self) -> c.Generator[None, None, None]: + """A context manager that temporarily disables the signal. No receivers + will be called if the signal is sent, until the ``with`` block exits. + Useful for tests. + """ + self.is_muted = True + + try: + yield None + finally: + self.is_muted = False + + def send( + self, + sender: t.Any | None = None, + /, + *, + _async_wrapper: c.Callable[ + [c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]], c.Callable[..., t.Any] + ] + | None = None, + **kwargs: t.Any, + ) -> list[tuple[c.Callable[..., t.Any], t.Any]]: + """Call all receivers that are connected to the given ``sender`` + or :data:`ANY`. Each receiver is called with ``sender`` as a positional + argument along with any extra keyword arguments. Return a list of + ``(receiver, return value)`` tuples. + + The order receivers are called is undefined, but can be influenced by + setting :attr:`set_class`. + + If a receiver raises an exception, that exception will propagate up. + This makes debugging straightforward, with an assumption that correctly + implemented receivers will not raise. + + :param sender: Call receivers connected to this sender, in addition to + those connected to :data:`ANY`. + :param _async_wrapper: Will be called on any receivers that are async + coroutines to turn them into sync callables. For example, could run + the receiver with an event loop. + :param kwargs: Extra keyword arguments to pass to each receiver. + + .. versionchanged:: 1.7 + Added the ``_async_wrapper`` argument. + """ + if self.is_muted: + return [] + + results = [] + + for receiver in self.receivers_for(sender): + if iscoroutinefunction(receiver): + if _async_wrapper is None: + raise RuntimeError("Cannot send to a coroutine function.") + + result = _async_wrapper(receiver)(sender, **kwargs) + else: + result = receiver(sender, **kwargs) + + results.append((receiver, result)) + + return results + + async def send_async( + self, + sender: t.Any | None = None, + /, + *, + _sync_wrapper: c.Callable[ + [c.Callable[..., t.Any]], c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]] + ] + | None = None, + **kwargs: t.Any, + ) -> list[tuple[c.Callable[..., t.Any], t.Any]]: + """Await all receivers that are connected to the given ``sender`` + or :data:`ANY`. Each receiver is called with ``sender`` as a positional + argument along with any extra keyword arguments. Return a list of + ``(receiver, return value)`` tuples. + + The order receivers are called is undefined, but can be influenced by + setting :attr:`set_class`. + + If a receiver raises an exception, that exception will propagate up. + This makes debugging straightforward, with an assumption that correctly + implemented receivers will not raise. + + :param sender: Call receivers connected to this sender, in addition to + those connected to :data:`ANY`. + :param _sync_wrapper: Will be called on any receivers that are sync + callables to turn them into async coroutines. For example, + could call the receiver in a thread. + :param kwargs: Extra keyword arguments to pass to each receiver. + + .. versionadded:: 1.7 + """ + if self.is_muted: + return [] + + results = [] + + for receiver in self.receivers_for(sender): + if not iscoroutinefunction(receiver): + if _sync_wrapper is None: + raise RuntimeError("Cannot send to a non-coroutine function.") + + result = await _sync_wrapper(receiver)(sender, **kwargs) + else: + result = await receiver(sender, **kwargs) + + results.append((receiver, result)) + + return results + + def has_receivers_for(self, sender: t.Any) -> bool: + """Check if there is at least one receiver that will be called with the + given ``sender``. A receiver connected to :data:`ANY` will always be + called, regardless of sender. Does not check if weakly referenced + receivers are still live. See :meth:`receivers_for` for a stronger + search. + + :param sender: Check for receivers connected to this sender, in addition + to those connected to :data:`ANY`. + """ + if not self.receivers: + return False + + if self._by_sender[ANY_ID]: + return True + + if sender is ANY: + return False + + return make_id(sender) in self._by_sender + + def receivers_for( + self, sender: t.Any + ) -> c.Generator[c.Callable[..., t.Any], None, None]: + """Yield each receiver to be called for ``sender``, in addition to those + to be called for :data:`ANY`. Weakly referenced receivers that are not + live will be disconnected and skipped. + + :param sender: Yield receivers connected to this sender, in addition + to those connected to :data:`ANY`. + """ + # TODO: test receivers_for(ANY) + if not self.receivers: + return + + sender_id = make_id(sender) + + if sender_id in self._by_sender: + ids = self._by_sender[ANY_ID] | self._by_sender[sender_id] + else: + ids = self._by_sender[ANY_ID].copy() + + for receiver_id in ids: + receiver = self.receivers.get(receiver_id) + + if receiver is None: + continue + + if isinstance(receiver, weakref.ref): + strong = receiver() + + if strong is None: + self._disconnect(receiver_id, ANY_ID) + continue + + yield strong + else: + yield receiver + + def disconnect(self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY) -> None: + """Disconnect ``receiver`` from being called when the signal is sent by + ``sender``. + + :param receiver: A connected receiver callable. + :param sender: Disconnect from only this sender. By default, disconnect + from all senders. + """ + sender_id: c.Hashable + + if sender is ANY: + sender_id = ANY_ID + else: + sender_id = make_id(sender) + + receiver_id = make_id(receiver) + self._disconnect(receiver_id, sender_id) + + if ( + "receiver_disconnected" in self.__dict__ + and self.receiver_disconnected.receivers + ): + self.receiver_disconnected.send(self, receiver=receiver, sender=sender) + + def _disconnect(self, receiver_id: c.Hashable, sender_id: c.Hashable) -> None: + if sender_id == ANY_ID: + if self._by_receiver.pop(receiver_id, None) is not None: + for bucket in self._by_sender.values(): + bucket.discard(receiver_id) + + self.receivers.pop(receiver_id, None) + else: + self._by_sender[sender_id].discard(receiver_id) + self._by_receiver[receiver_id].discard(sender_id) + + def _make_cleanup_receiver( + self, receiver_id: c.Hashable + ) -> c.Callable[[weakref.ref[c.Callable[..., t.Any]]], None]: + """Create a callback function to disconnect a weakly referenced + receiver when it is garbage collected. + """ + + def cleanup(ref: weakref.ref[c.Callable[..., t.Any]]) -> None: + # If the interpreter is shutting down, disconnecting can result in a + # weird ignored exception. Don't call it in that case. + if not sys.is_finalizing(): + self._disconnect(receiver_id, ANY_ID) + + return cleanup + + def _make_cleanup_sender( + self, sender_id: c.Hashable + ) -> c.Callable[[weakref.ref[t.Any]], None]: + """Create a callback function to disconnect all receivers for a weakly + referenced sender when it is garbage collected. + """ + assert sender_id != ANY_ID + + def cleanup(ref: weakref.ref[t.Any]) -> None: + self._weak_senders.pop(sender_id, None) + + for receiver_id in self._by_sender.pop(sender_id, ()): + self._by_receiver[receiver_id].discard(sender_id) + + return cleanup + + def _cleanup_bookkeeping(self) -> None: + """Prune unused sender/receiver bookkeeping. Not threadsafe. + + Connecting & disconnecting leaves behind a small amount of bookkeeping + data. Typical workloads using Blinker, for example in most web apps, + Flask, CLI scripts, etc., are not adversely affected by this + bookkeeping. + + With a long-running process performing dynamic signal routing with high + volume, e.g. connecting to function closures, senders are all unique + object instances. Doing all of this over and over may cause memory usage + to grow due to extraneous bookkeeping. (An empty ``set`` for each stale + sender/receiver pair.) + + This method will prune that bookkeeping away, with the caveat that such + pruning is not threadsafe. The risk is that cleanup of a fully + disconnected receiver/sender pair occurs while another thread is + connecting that same pair. If you are in the highly dynamic, unique + receiver/sender situation that has lead you to this method, that failure + mode is perhaps not a big deal for you. + """ + for mapping in (self._by_sender, self._by_receiver): + for ident, bucket in list(mapping.items()): + if not bucket: + mapping.pop(ident, None) + + def _clear_state(self) -> None: + """Disconnect all receivers and senders. Useful for tests.""" + self._weak_senders.clear() + self.receivers.clear() + self._by_sender.clear() + self._by_receiver.clear() + + +class NamedSignal(Signal): + """A named generic notification emitter. The name is not used by the signal + itself, but matches the key in the :class:`Namespace` that it belongs to. + + :param name: The name of the signal within the namespace. + :param doc: The docstring for the signal. + """ + + def __init__(self, name: str, doc: str | None = None) -> None: + super().__init__(doc) + + #: The name of this signal. + self.name: str = name + + def __repr__(self) -> str: + base = super().__repr__() + return f"{base[:-1]}; {self.name!r}>" # noqa: E702 + + +class Namespace(dict[str, NamedSignal]): + """A dict mapping names to signals.""" + + def signal(self, name: str, doc: str | None = None) -> NamedSignal: + """Return the :class:`NamedSignal` for the given ``name``, creating it + if required. Repeated calls with the same name return the same signal. + + :param name: The name of the signal. + :param doc: The docstring of the signal. + """ + if name not in self: + self[name] = NamedSignal(name, doc) + + return self[name] + + +class _PNamespaceSignal(t.Protocol): + def __call__(self, name: str, doc: str | None = None) -> NamedSignal: ... + + +default_namespace: Namespace = Namespace() +"""A default :class:`Namespace` for creating named signals. :func:`signal` +creates a :class:`NamedSignal` in this namespace. +""" + +signal: _PNamespaceSignal = default_namespace.signal +"""Return a :class:`NamedSignal` in :data:`default_namespace` with the given +``name``, creating it if required. Repeated calls with the same name return the +same signal. +""" diff --git a/web_viewer/.venv/lib/python3.12/site-packages/blinker/py.typed b/web_viewer/.venv/lib/python3.12/site-packages/blinker/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/INSTALLER b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/METADATA b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/METADATA new file mode 100644 index 0000000..534eb57 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/METADATA @@ -0,0 +1,84 @@ +Metadata-Version: 2.4 +Name: click +Version: 8.3.0 +Summary: Composable command line interface toolkit +Maintainer-email: Pallets +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-Expression: BSD-3-Clause +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +License-File: LICENSE.txt +Requires-Dist: colorama; platform_system == 'Windows' +Project-URL: Changes, https://click.palletsprojects.com/page/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/click/ + +
+ +# 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 + + +## A Simple Example + +```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() +``` + +``` +$ 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 + +## Contributing + +See our [detailed contributing documentation][contrib] for many ways to +contribute, including reporting issues, requesting features, asking or answering +questions, and making PRs. + +[contrib]: https://palletsprojects.com/contributing/ + diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/RECORD b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/RECORD new file mode 100644 index 0000000..d3927d9 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/RECORD @@ -0,0 +1,40 @@ +click-8.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +click-8.3.0.dist-info/METADATA,sha256=P6vpEHZ_MLBt4SO2eB-QaadcOdiznkzaZtJImRo7_V4,2621 +click-8.3.0.dist-info/RECORD,, +click-8.3.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +click-8.3.0.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 +click/__init__.py,sha256=6YyS1aeyknZ0LYweWozNZy0A9nZ_11wmYIhv3cbQrYo,4473 +click/__pycache__/__init__.cpython-312.pyc,, +click/__pycache__/_compat.cpython-312.pyc,, +click/__pycache__/_termui_impl.cpython-312.pyc,, +click/__pycache__/_textwrap.cpython-312.pyc,, +click/__pycache__/_utils.cpython-312.pyc,, +click/__pycache__/_winconsole.cpython-312.pyc,, +click/__pycache__/core.cpython-312.pyc,, +click/__pycache__/decorators.cpython-312.pyc,, +click/__pycache__/exceptions.cpython-312.pyc,, +click/__pycache__/formatting.cpython-312.pyc,, +click/__pycache__/globals.cpython-312.pyc,, +click/__pycache__/parser.cpython-312.pyc,, +click/__pycache__/shell_completion.cpython-312.pyc,, +click/__pycache__/termui.cpython-312.pyc,, +click/__pycache__/testing.cpython-312.pyc,, +click/__pycache__/types.cpython-312.pyc,, +click/__pycache__/utils.cpython-312.pyc,, +click/_compat.py,sha256=v3xBZkFbvA1BXPRkFfBJc6-pIwPI7345m-kQEnpVAs4,18693 +click/_termui_impl.py,sha256=ktpAHyJtNkhyR-x64CQFD6xJQI11fTA3qg2AV3iCToU,26799 +click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400 +click/_utils.py,sha256=kZwtTf5gMuCilJJceS2iTCvRvCY-0aN5rJq8gKw7p8g,943 +click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465 +click/core.py,sha256=1A5T8UoAXklIGPTJ83_DJbVi35ehtJS2FTkP_wQ7es0,128855 +click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461 +click/exceptions.py,sha256=8utf8w6V5hJXMnO_ic1FNrtbwuEn1NUu1aDwV8UqnG4,9954 +click/formatting.py,sha256=RVfwwr0rwWNpgGr8NaHodPzkIr7_tUyVh_nDdanLMNc,9730 +click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923 +click/parser.py,sha256=Q31pH0FlQZEq-UXE_ABRzlygEfvxPTuZbWNh4xfXmzw,19010 +click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click/shell_completion.py,sha256=Cc4GQUFuWpfQBa9sF5qXeeYI7n3tI_1k6ZdSn4BZbT0,20994 +click/termui.py,sha256=vAYrKC2a7f_NfEIhAThEVYfa__ib5XQbTSCGtJlABRA,30847 +click/testing.py,sha256=EERbzcl1br0mW0qBS9EqkknfNfXB9WQEW0ELIpkvuSs,19102 +click/types.py,sha256=ek54BNSFwPKsqtfT7jsqcc4WHui8AIFVMKM4oVZIXhc,39927 +click/utils.py,sha256=gCUoewdAhA-QLBUUHxrLh4uj6m7T1WjZZMNPvR0I7YA,20257 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/WHEEL b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/licenses/LICENSE.txt b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..d12a849 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click-8.3.0.dist-info/licenses/LICENSE.txt @@ -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/web_viewer/.venv/lib/python3.12/site-packages/click/__init__.py b/web_viewer/.venv/lib/python3.12/site-packages/click/__init__.py new file mode 100644 index 0000000..1aa547c --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/__init__.py @@ -0,0 +1,123 @@ +""" +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 __future__ import annotations + +from .core import Argument as Argument +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 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 .termui import clear as clear +from .termui import confirm as confirm +from .termui import echo_via_pager as echo_via_pager +from .termui import edit as edit +from .termui import getchar as getchar +from .termui import launch as launch +from .termui import pause as pause +from .termui import progressbar as progressbar +from .termui import prompt as prompt +from .termui import secho as secho +from .termui import style as style +from .termui import unstyle as unstyle +from .types import BOOL as BOOL +from .types import Choice as Choice +from .types import DateTime as DateTime +from .types import File as File +from .types import FLOAT as FLOAT +from .types import FloatRange as FloatRange +from .types import INT as INT +from .types import IntRange as IntRange +from .types import ParamType as ParamType +from .types import Path as Path +from .types import STRING as STRING +from .types import Tuple as Tuple +from .types import UNPROCESSED as UNPROCESSED +from .types import UUID as UUID +from .utils import echo as echo +from .utils import format_filename as format_filename +from .utils import get_app_dir as get_app_dir +from .utils import get_binary_stream as get_binary_stream +from .utils import get_text_stream as get_text_stream +from .utils import open_file as open_file + + +def __getattr__(name: str) -> object: + import warnings + + if name == "BaseCommand": + from .core import _BaseCommand + + warnings.warn( + "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Command' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _BaseCommand + + if name == "MultiCommand": + from .core import _MultiCommand + + warnings.warn( + "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Group' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _MultiCommand + + if name == "OptionParser": + from .parser import _OptionParser + + warnings.warn( + "'OptionParser' is deprecated and will be removed in Click 9.0. The" + " old parser is available in 'optparse'.", + DeprecationWarning, + stacklevel=2, + ) + return _OptionParser + + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " Click 9.1. Use feature detection or" + " 'importlib.metadata.version(\"click\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("click") + + raise AttributeError(name) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d21cca08de23f212a4267a0205b8a2b290ddb777 GIT binary patch literal 4081 zcmbW4OK%&=5yyuT-%maKkZnCil5L8%L`t$PTeker)7EBJGAtR3bPj_dXC#e1oEh{C zrIleVf*^wCIJHP?qLHO1QyuM!8tjwbJSqIn<_MgA5?yl~x z9#)P1n#m*;d=CGau?~9`pqQH291-!+gd*TOAMs%xA&p z#5v{x@Og2b`5gFyxWIfKJSYa4FMx-{5c42-SPU}{fk(s$^DtNyD)WdvS{)N(%qnds;s*0HcuveQ&wy`=o6NJ| zTjCb;HSoNcXTA=;Ep9X40N)XJnCHL?VuAT4_^!Cid<%R}++&^x-xv3pZ-XC*2h4ZC zi(-*^!CtB^i)H4!;1#jLd=I=TR+;aE*Tfq01Moxfka-dONIU|s7)!qni}l0h!s?59 za@n>j&sED;b=9}3o^7g+4}(3|QLC;|L(6h}&yuF0mJiin&s6=uu&uJ{dV!})-&6xv zt?JLscZndgAhH6_523Mrz1-`mdbBgLy$F`oEstYf&<=vlDtsw8KW!*Q8 zq%PeWW*%>3@$ovA)C1LUO<#4~fSjA^ffejwS*%;D>|ya;tD@41mY+nw=lXitHjBwO z^m8wS>M7m9R6Vd<$A1%Q+Tx3Hw^lWs0R6G$=8l2AEnIeO+pN%xX1^1dpV83BL+sE4 zL^r)wZ{o2o^{N?|66Uzx;TC(LK)~Ezah)AYR(TH^BG+^r*=>1C?U}ZBV(+JTYM$=< znqgL4sRu4Oj~-7$@gwS=gG#Yg^K=bRILy&&=v)6|;Sm#F}NBt5Whtdp@yzACE%kl=OzXh1)s~TataZ zXJj5B0BbHqs2Rw_fz&;%b-eyvGteqEDRE!g$*Ch1+tevAu~v9#?`CDs)%GnN;oIda z37dw+w+tthJsuym^_o-J!|;fw*L*s2%9HM{G=0C^9D!!l;~V$MBMtR~Lz{-iYEB!& zOPiY?!xUZKbFB(>##i*f+_tL3VTuX9BWoXTE^Y&oYqqNgPjrXF+`Ya*#`wB(Y-&DB z+lL-cf2;?z$LQAfll6^<EwpC=&obmQ@p&E?gtt<@DY!%v^C^LJ~jxgCzAwuAK@ zJexFwLa2M5W>^%lUTT&tN0*11A4pTL!k8l?h5W>na6QvuKNb4nv_BC5N@(ca=dhxX zh7844jzTXLqwD#EqDYlXDaz^*%IeF|vsioL$+r;+;_a3Z{Y&f%_@fWTcWo)~6hHQq zK!J`Nb$`%P@`Ikb*;DG+Q`(MJV@D|OAJ|jogPy|dDa)Sj@Ok4$k>hj!_z80#^cH#6 z*F0^fK0DpnYO>MuMJw)GN^rK_|59mNe-$e9Zb*8ssi&5ZLR;xehGD^trnL^^i|u?z zD^D5UFg=G4-G(O`~)*kBF#rBz- z2|rp7Q(lrA@WG3+>!!=Zu}eRSZk0bjluLW4{3ZRsEUmZ|uH1e};-pgBkyHmAqjX@F zk@?L7Q-gE@8C{tNq4!ZG zGeTQR@s^_4Qda3X_f2l}%iQQUxx$yZ!k_YA=Vo8UXTMJl{PEP+$>CSw;deNuvQY@j zPw`#vMp39orHMz)!Fxyf8J@MmNA;Md8E!?>>Pb!8snK(YNKxXJnbXA{izbfz*@DvPdtfQYGssS!27lTh?t~j!`R$*NnD|Ws*Dz z%9|n!Bv(kTl1!3JkrW~INPq(F%Mux;NoGi9Nv@GxC%Hi~M{<+o7RfxxZIU}A3y^xG z7FgKaT{7Gwxli(dWRYZvgdWyEEAkh_KZpDfUueF(-;)-aR6_i}LEd(ULZN?kMMB|! zXO+p^px#*q7_YPaHV=ksb+Tm{ZC63YSb55|#(GTu<~45JO@YC{>~W})2$;}kPS-e8_*D7=r(ghu$h F{{e1ZZ3_SZ literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6d5ab61c8a1038467e391641f9e1f5141cb27cf GIT binary patch literal 24203 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 z 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 QiY$L7c=$GDF`4s!0xwC79{>OV literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b344ee7b1adf970f2f2960d1f4356cff56e823a GIT binary patch literal 31620 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2a5831c340ac0991b9d60b894d47200663db7f9 GIT binary patch literal 2434 zcmaJ@+iTla7(cpLmM@7D=hC`~Q#Wa{Xl+p#2J45 z+)E)Ey@eEZ0Vy8UlWeJm*(X+++BD8*X@Pr8CIaOKWWz9R+15+8)sBj;S_p-U9HD~lD!H88ZOHUyc>8Q zQhk8snHfk`-+R@P1qtb>?H{VCY^v^$9M)mx;rtY8m9DMjJPu0M@vaql;sJ+J=m?-3 z=Wq&gJV#K@>u?U78X(@GlZ_ecu8w!Lo{K^bPa<3aO%^${75fhlD(S5|H#~MX{5)C~ zq6o~`V-o)HKQIK=Wm58-+cJSwd0Ns<XU8=tFtP$ zR7FZmrHyIJ6)Rs-YQZJ*OSo{d&>1&Gjc+ebam2pSzWelmr0~SVAZmGkUq9M zb`s*0nNmeLqlzPDBArqV+Y+&?$XJ@hgb9WsX4DC3R#P)77Kdh4V^-9)32`QCPnpKK zp)+SiOS9EekZ>9ZV~Gh}OH7Lr;ZO!%?9fcsZIz~EL(!q2-7U^X8(#d-3u;oMg+bg! zPn%I^q|mW)=!>J@9^2?RnfGr6LZv`#JrFCm^{$*PwhiP*%HhtXI|ct*xG#U5;Q6IH zH5_ceH@-Mtxc(p*EBE&2#|ziigGYaEjg;$(@lv-~>=rk=&uj+IRu~jMvJF{LqLEI7 zihYENzW{g}QNVm495FjEa`elIm}49?N4?C$q2`-NHfb+X)v_^rU9)Go_aXS8A$(3R zLt}6tZ$kvgChb{eq4fXLXV4t;A<9tSGZ{*t#su1J)(p+oWL?q>1=!%WTImT(y`2VJ zUlLf{3}v}o-6Cx23aIvZb&$0eJS}pTgnRf92@nL1V7VT3{%|8D8Z}*DYfXdr13kNo zwxY*M(bL7~={5dHId;4hyI71}T;rof&Y?1HZzNNpd9?19cFf)9Ty3eu05p>@lA~@1A zEHL*4vc8Wbq?1(p)lUGH(Jhpt8+sO%thY{3?u~;TVg%iM4WW#0j3sfS9bznfR zV-#w71iD)gEVu(|n1ThJB;U`0;ds7;-uWf^ihkaaK6{;oJ0%%7U=?|yxfKd8z57X~9Ep}9Vlg5vaa+OWd!vh^h0X%M(!Rp2b-uD0 z9IP~fbEO5fwBO4vW((m@b8CDzCU?k~;Kw0&?)Y@gvTs;6zKO{ti%$^I3IcX#*f>n4 zC&`qaH!|3Bc4scoiO z_Bjafd@DQrmD(A+fiHoAG&>7AG^#KZMLj~jzoVXqDD(*RKjltP(WSvsc%T>__zS_L G8suN1C_N?s literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08a15cdf688f991505b09436be745f89da934384 GIT binary patch literal 1209 zcmZ`&O-~a+7@paWwv-m32u2OMdRqg#;0F*iMiVNUG>r)#iF+BA-2t|2ck9e9SmS{M zF%l1GJoI3^V?6i={0CmzlXZ=Wi6=cl;Ka$9E;Tj8N%o!R{oLn$XXbM@n+9xEe`lSK zA^<t%~g;@&19;=F3Rn zM5!mnozh5l0p{uLw8hv+F^^Qro%G=3S!OiS|MjO@{_d)v%D;HJ3Zrh*^ZdXJ9N(iw ztFxvMEAZet12uqw0d2qn+G6|`yc1`EDin{PrixJ)LY8Y%icpk7$TQ2BA&Kv7^b*DHGUbGnqeXqy zFJs-@!1}arg=IDvsuR;T30fw;7huoUH}Dc#ckl)#y0(tJb=`HA^tjt=+SpZ{IsqQ3 znAV!Pf~jt~jwQ;t zFYQi$eDMCk?#=qCiF#_{fcIHcg(yLA5E3sEX+YEz>0;(6jZoRQLzj1%LFjR4y2m{y z5wd-YnG^m}30pxV&lhL&b5ZZT!rd8E%;%?P(Zb!re4b=kq4$9BCqXh?@JH7YNe1W; z6I)X&le%>JP=fMgoXL~a3@5Z4GMbDK;inx*m|ept5mYOV zw-Ui1lK8!h|1rP<$RLkO7T73>=|;&p6!Xl@p%YBc@c6$xk5aed-ft6yIry{Pd_hk(Jo3!tlSr)RroQ*MIEAN}XQ^ba@shwGqsPmrLr pmjT_G9d%oMm2afaZ6=%G#*cx)+N0Nx>KFCKz-S%hn&3{0#2+IY9WMX? literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc3e11e068bf057307c604df22c32467826a2d71 GIT binary patch literal 11779 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff47597d0932ba2bcff6ed43eab302931dfb31cc GIT binary patch literal 133440 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 KB`DYNME`#-bfek; literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..750ad0629302c2d298c569d8b35f6990f5923fad GIT binary patch literal 22146 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88a397be46e74bc6c908f8c93346f87790278429 GIT binary patch literal 14785 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 W&s)YU{FaY6Djy8ls`*1q$o>bt){3(L literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..326617636bb19e1dfef165f2466006e00b4f9828 GIT binary patch literal 2974 zcmbVOO>7&-6`olxDT+TLqsWfyCdxR?Pno!+t)M?3in_LHr%DVZuywJ5f*S4&#g&)4 z#O#n11*Mf-18&emdUFpsv_R3uaSuKA+FQ|HWVnIdtqr(HF7C;wTA*+b?R&FJnO5B% zIsj+q=gpgW-+S+S{C%;QBhZF_DZ1aEAmme=jG7$}=E$#fGPPD19ewmw8DWdGH_^#1XDP9WOkYnnmnY%G%p=LsNEAd-WG+9F%l{X- zDNzy=<8pAaB(j&&%Xv{2lb7w~f_O^g;9C^a;%SkGovEQ!nYoWwRcMswz8`3=-M|kQ zVV?E^hkN&FygM-;_*$-O2j}B0MF`zRaITUexkeYSK^J90*OR^uQJ6jCwRX^vH9nBF zZv{@TgF7|F1y_xhLbblA4P>*?cjZ8;TD33ze$8{6weC>21ON5v@wr;)YWbxucP{c( z8P*)nbuQLcy`agxu-Y9)Pp?YdaC%CC?uHXTRTbfObV8VhZj0PLv#<*T^1n?`SrAc^ z*kLflCMZ;6^qH=V8;Q~rr_>ZoA5)532A7}1;3}CUdJnZV3zqgZL3I*cgsf%uZNgMo zpM(B-a`HU+0@3(L)3k|L9JZ_%i8}Ni-6UZ_qv@uJ)b-@+&(Y`}+12RR0f zJB|$Z>4;szg65iZGy}JH825*Rwp4PY60~yG36x}Rrwa~tb*#I*3c^HnIy~2ca0>yQpM*2_)qnWv(QPfp#hbz|`;;UOdkF^5D+S2Pv6|%7Bw9$}6 zwRGCS3LCUtSnas0Z4Jg6B`$=klA#8(U7tB^?ytK3Dsy!R5^dghL1&XADsb^*D8>j> zb4JZlz`DJ#{UnK_tlp8jU0+Eut{OT)SFVgBo+znSSs&gGCUPO9sMpz-tH)yf^)6Su z!{RioGq=S;sS&=FCDj9E_%daD&HCKy0puZDSqyyG0$D#8x!XJzeLB|APkrusyy?Zh zlYo7T_dEnCfdFLSGR56cGX5kbb-4~6JlFHJ+mWZ03e-x~$SF*K`~QNrabyX@1D^&D z;^0e&fX9Pzu`&@+9oY^Kb(Cos#){IR2A+tfPHL^(W>3fJquhco


kDvV!Tu~Z)J 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 P9J%+?_C|gqTfP4m5fJ^} literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..757eb1ccb4b49c345bb8fd5da8d944c55fba7bd5 GIT binary patch literal 23346 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b544c7d28961f2b92b3cc3d8f62860ff0bbc65e5 GIT binary patch literal 27418 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 c&!4nBB8po-5-9xSSqX!JNx)(la3wj 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 LpG)|`%lrQUzn>^( literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..130ca7159e5bb4e8ecf5df1e611bbb2eb539a8c7 GIT binary patch literal 24888 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/_compat.py b/web_viewer/.venv/lib/python3.12/site-packages/click/_compat.py new file mode 100644 index 0000000..f2726b9 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/_compat.py @@ -0,0 +1,622 @@ +from __future__ import annotations + +import codecs +import collections.abc as cabc +import io +import os +import re +import sys +import typing as t +from types import TracebackType +from weakref import WeakKeyDictionary + +CYGWIN = sys.platform.startswith("cygwin") +WIN = sys.platform.startswith("win") +auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None +_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") + + +def _make_text_stream( + stream: t.BinaryIO, + encoding: str | None, + errors: str | None, + 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[t.Any]) -> 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: str | None, + errors: str | None, + 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(b"") + 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[t.Any], 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[t.Any], 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.Any]) -> t.BinaryIO | None: + # 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.Any]) -> t.BinaryIO | None: + # 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: str | None) -> 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: str | None, errors: str | None +) -> 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[t.Any], + encoding: str | None, + errors: str | None, + is_binary: t.Callable[[t.IO[t.Any], bool], bool], + find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None], + 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[t.Any], + encoding: str | None, + errors: str | None, + 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[t.Any], + encoding: str | None, + errors: str | None, + 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: str | None = None, errors: str | None = 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: str | None = None, errors: str | None = 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: str | None = None, errors: str | None = 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: str | os.PathLike[str] | int, + mode: str, + encoding: str | None, + errors: str | None, +) -> t.IO[t.Any]: + """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 | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + atomic: bool = False, +) -> tuple[t.IO[t.Any], bool]: + binary = "b" in mode + filename = os.fspath(filename) + + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if binary: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + return _wrap_io_open(filename, mode, encoding, errors), True + + # Some usability stuff for atomic writes + if "a" in mode: + raise ValueError( + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." + ) + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import errno + import random + + try: + perm: int | None = 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[t.Any], af), True + + +class _AtomicFile: + def __init__(self, f: t.IO[t.Any], 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: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + 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[t.Any]) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") + + +def should_strip_ansi( + stream: t.IO[t.Any] | None = None, color: bool | None = 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: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = 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: str) -> int: + try: + return _write(s) + except BaseException: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write # type: ignore[method-assign] + + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + + return rv + +else: + + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: str | None, errors: str | None + ) -> t.TextIO | None: + return None + + +def term_len(x: str) -> int: + return len(strip_ansi(x)) + + +def isatty(stream: t.IO[t.Any]) -> bool: + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func( + src_func: t.Callable[[], t.TextIO | None], + wrapper_func: t.Callable[[], t.TextIO], +) -> t.Callable[[], t.TextIO | None]: + cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.TextIO | None: + stream = src_func() + + if stream is None: + return None + + 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: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, +} + +text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, +} diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/_termui_impl.py b/web_viewer/.venv/lib/python3.12/site-packages/click/_termui_impl.py new file mode 100644 index 0000000..47f87b8 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/_termui_impl.py @@ -0,0 +1,847 @@ +""" +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. +""" + +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import math +import os +import shlex +import sys +import time +import typing as t +from gettext import gettext as _ +from io import StringIO +from pathlib import Path +from types import TracebackType + +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: cabc.Iterable[V] | None, + length: int | None = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + label: str | None = None, + file: t.TextIO | None = None, + color: bool | None = 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.hidden = hidden + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label: str = label or "" + + if file is None: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + file = StringIO() + + self.file = file + self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 + self.width: int = width + self.autowidth: bool = 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("cabc.Iterable[V]", range(length)) + self.iter: cabc.Iterable[V] = iter(iterable) + self.length = length + self.pos: int = 0 + self.avg: list[float] = [] + self.last_eta: float + self.start: float + self.start = self.last_eta = time.time() + self.eta_known: bool = False + self.finished: bool = False + self.max_width: int | None = None + self.entered: bool = False + self.current_item: V | None = None + self._is_atty = isatty(self.file) + self._last_line: str | None = None + + def __enter__(self) -> ProgressBar[V]: + self.entered = True + self.render_progress() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.render_finish() + + def __iter__(self) -> cabc.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.hidden or not self._is_atty: + 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: + if self.hidden: + return + + if not self._is_atty: + # Only output the label once if the output is not a TTY. + 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: + import shutil + + 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 and self.max_width is not None: + buf.append(BEFORE_BAR) + buf.append(" " * self.max_width) + 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: V | None = 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) -> cabc.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 not self._is_atty: + 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: cabc.Iterable[str], color: bool | None = None) -> None: + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if stdout is None: + stdout = StringIO() + + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, generator, color) + + # Split and normalize the pager command into parts. + pager_cmd_parts = shlex.split(os.environ.get("PAGER", ""), posix=False) + if pager_cmd_parts: + if WIN: + if _tempfilepager(generator, pager_cmd_parts, color): + return + elif _pipepager(generator, pager_cmd_parts, color): + return + + if os.environ.get("TERM") in ("dumb", "emacs"): + return _nullpager(stdout, generator, color) + if (WIN or sys.platform.startswith("os2")) and _tempfilepager( + generator, ["more"], color + ): + return + if _pipepager(generator, ["less"], color): + return + + import tempfile + + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if _pipepager(generator, ["more"], color): + return + return _nullpager(stdout, generator, color) + finally: + os.unlink(filename) + + +def _pipepager( + generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None +) -> bool: + """Page through text by feeding it to another program. Invoking a + pager through this might support colors. + + Returns `True` if the command was found, `False` otherwise and thus another + pager should be attempted. + """ + # Split the command into the invoked CLI and its parameters. + if not cmd_parts: + return False + + import shutil + + cmd = cmd_parts[0] + cmd_params = cmd_parts[1:] + + cmd_filepath = shutil.which(cmd) + if not cmd_filepath: + return False + # Resolves symlinks and produces a normalized absolute path string. + cmd_path = Path(cmd_filepath).resolve() + cmd_name = cmd_path.name + + import subprocess + + # Make a local copy of the environment to not affect the global one. + env = dict(os.environ) + + # If we're piping to less and the user hasn't decided on colors, we enable + # them by default we find the -R flag in the command line arguments. + if color is None and cmd_name == "less": + less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_params)}" + if not less_flags: + env["LESS"] = "-R" + color = True + elif "r" in less_flags or "R" in less_flags: + color = True + + c = subprocess.Popen( + [str(cmd_path)] + cmd_params, + shell=True, + stdin=subprocess.PIPE, + env=env, + errors="replace", + text=True, + ) + assert c.stdin is not None + try: + for text in generator: + if not color: + text = strip_ansi(text) + + c.stdin.write(text) + except BrokenPipeError: + # In case the pager exited unexpectedly, ignore the broken pipe error. + pass + except Exception as e: + # In case there is an exception we want to close the pager immediately + # and let the caller handle it. + # Otherwise the pager will keep running, and the user may not notice + # the error message, or worse yet it may leave the terminal in a broken state. + c.terminate() + raise e + finally: + # We must close stdin and wait for the pager to exit before we continue + try: + c.stdin.close() + # Close implies flush, so it might throw a BrokenPipeError if the pager + # process exited already. + except BrokenPipeError: + pass + + # 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 + + return True + + +def _tempfilepager( + generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None +) -> bool: + """Page through text by invoking a program on a temporary file. + + Returns `True` if the command was found, `False` otherwise and thus another + pager should be attempted. + """ + # Split the command into the invoked CLI and its parameters. + if not cmd_parts: + return False + + import shutil + + cmd = cmd_parts[0] + + cmd_filepath = shutil.which(cmd) + if not cmd_filepath: + return False + # Resolves symlinks and produces a normalized absolute path string. + cmd_path = Path(cmd_filepath).resolve() + + import subprocess + 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: + subprocess.call([str(cmd_path), filename]) + except OSError: + # Command not found + pass + finally: + os.close(fd) + os.unlink(filename) + + return True + + +def _nullpager( + stream: t.TextIO, generator: cabc.Iterable[str], color: bool | None +) -> 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: str | None = None, + env: cabc.Mapping[str, str] | None = 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" + + from shutil import which + + for editor in "sensible-editor", "vim", "nano": + if which(editor) is not None: + return editor + return "vi" + + def edit_files(self, filenames: cabc.Iterable[str]) -> None: + import subprocess + + editor = self.get_editor() + environ: dict[str, str] | None = None + + if self.env: + environ = os.environ.copy() + environ.update(self.env) + + exc_filename = " ".join(f'"{filename}"' for filename in filenames) + + try: + c = subprocess.Popen( + args=f"{editor} {exc_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 + + @t.overload + def edit(self, text: bytes | bytearray) -> bytes | None: ... + + # We cannot know whether or not the type expected is str or bytes when None + # is passed, so str is returned as that was what was done before. + @t.overload + def edit(self, text: str | None) -> str | None: ... + + def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None: + import tempfile + + if text is None: + data: bytes | bytearray = 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_files((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") + 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) + args = ["explorer", f"/select,{url}"] + else: + args = ["start"] + if wait: + args.append("/WAIT") + args.append("") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 + elif CYGWIN: + if locate: + url = _unquote_file(url) + args = ["cygstart", os.path.dirname(url)] + else: + args = ["cygstart"] + if wait: + args.append("-w") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 + + 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) -> None: + 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 sys.platform == "win32": + import msvcrt + + @contextlib.contextmanager + def raw_terminal() -> cabc.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`. + + if echo: + func = t.cast(t.Callable[[], str], msvcrt.getwche) + else: + func = t.cast(t.Callable[[], str], msvcrt.getwch) + + 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 termios + import tty + + @contextlib.contextmanager + def raw_terminal() -> cabc.Iterator[int]: + f: t.TextIO | None + 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/web_viewer/.venv/lib/python3.12/site-packages/click/_textwrap.py b/web_viewer/.venv/lib/python3.12/site-packages/click/_textwrap.py new file mode 100644 index 0000000..97fbee3 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/_textwrap.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import collections.abc as cabc +import textwrap +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + def _handle_long_word( + self, + reversed_chunks: list[str], + cur_line: 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) -> cabc.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/web_viewer/.venv/lib/python3.12/site-packages/click/_utils.py b/web_viewer/.venv/lib/python3.12/site-packages/click/_utils.py new file mode 100644 index 0000000..09fb008 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/_utils.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import enum +import typing as t + + +class Sentinel(enum.Enum): + """Enum used to define sentinel values. + + .. seealso:: + + `PEP 661 - Sentinel Values `_. + """ + + UNSET = object() + FLAG_NEEDS_VALUE = object() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + +UNSET = Sentinel.UNSET +"""Sentinel used to indicate that a value is not set.""" + +FLAG_NEEDS_VALUE = Sentinel.FLAG_NEEDS_VALUE +"""Sentinel used to indicate 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``. +""" + +T_UNSET = t.Literal[UNSET] # type: ignore[valid-type] +"""Type hint for the :data:`UNSET` sentinel value.""" + +T_FLAG_NEEDS_VALUE = t.Literal[FLAG_NEEDS_VALUE] # type: ignore[valid-type] +"""Type hint for the :data:`FLAG_NEEDS_VALUE` sentinel value.""" diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/_winconsole.py b/web_viewer/.venv/lib/python3.12/site-packages/click/_winconsole.py new file mode 100644 index 0000000..e56c7c6 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/_winconsole.py @@ -0,0 +1,296 @@ +# 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. +from __future__ import annotations + +import collections.abc as cabc +import io +import sys +import time +import typing as t +from ctypes import Array +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 + +if t.TYPE_CHECKING: + try: + # Using `typing_extensions.Buffer` instead of `collections.abc` + # on Windows for some reason does not have `Sized` implemented. + from collections.abc import Buffer # type: ignore + except ImportError: + from typing_extensions import Buffer + +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_ = [ # noqa: RUF012 + ("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: Buffer, writable: bool = False) -> Array[c_char]: + buf = Py_buffer() + flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + + try: + buffer_type = c_char * buf.len + out: Array[c_char] = buffer_type.from_address(buf.buf) + return out + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + def __init__(self, handle: int | None) -> None: + self.handle = handle + + def isatty(self) -> t.Literal[True]: + super().isatty() + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + def readable(self) -> t.Literal[True]: + return True + + def readinto(self, b: Buffer) -> int: + 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) -> t.Literal[True]: + return True + + @staticmethod + def _get_error_message(errno: int) -> str: + 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: Buffer) -> int: + 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: cabc.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) -> str: + 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: cabc.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: str | None, errors: str | None +) -> t.TextIO | None: + if ( + get_buffer is None + or encoding not in {"utf-16-le", None} + or errors not in {"strict", None} + or not _is_console(f) + ): + return None + + func = _stream_factories.get(f.fileno()) + if func is None: + return None + + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/core.py b/web_viewer/.venv/lib/python3.12/site-packages/click/core.py new file mode 100644 index 0000000..ff2f74a --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/core.py @@ -0,0 +1,3347 @@ +from __future__ import annotations + +import collections.abc as cabc +import enum +import errno +import inspect +import os +import sys +import typing as t +from collections import abc +from collections import Counter +from contextlib import AbstractContextManager +from contextlib import contextmanager +from contextlib import ExitStack +from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat +from types import TracebackType + +from . import types +from ._utils import FLAG_NEEDS_VALUE +from ._utils import UNSET +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import NoArgsIsHelpError +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 _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: + 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 +) -> cabc.Iterator[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(Group, 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_nested_chain( + base_command: Group, cmd_name: str, cmd: Command, register: bool = False +) -> None: + if not base_command.chain or not isinstance(cmd, Group): + return + + if register: + message = ( + f"It is not possible to add the group {cmd_name!r} to another" + f" group {base_command.name!r} that is in chain mode." + ) + else: + message = ( + f"Found the group {cmd_name!r} as subcommand to another group " + f" {base_command.name!r} that is in chain mode. This is not supported." + ) + + raise RuntimeError(message) + + +def batch(iterable: cabc.Iterable[V], batch_size: int) -> list[tuple[V, ...]]: + return list(zip(*repeat(iter(iterable), batch_size), strict=False)) + + +@contextmanager +def augment_usage_errors( + ctx: Context, param: Parameter | None = None +) -> cabc.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: cabc.Sequence[Parameter], + declaration_order: cabc.Sequence[Parameter], +) -> list[Parameter]: + """Returns all declared parameters in the order they should be processed. + + The declared parameters are re-shuffled depending on the order in which + they were invoked, as well as the eagerness of each parameters. + + The invocation order takes precedence over the declaration order. I.e. the + order in which the user provided them to the CLI is respected. + + This behavior and its effect on callback evaluation is detailed at: + https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order + """ + + def sort_key(item: Parameter) -> tuple[bool, float]: + try: + idx: float = invocation_order.index(item) + except ValueError: + idx = float("inf") + + return not item.is_eager, idx + + return sorted(declaration_order, key=sort_key) + + +class ParameterSource(enum.Enum): + """This is an :class:`~enum.Enum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + + +class Context: + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + :param show_default: Show the default value for commands. If this + value is not set, it defaults to the value from the parent + context. ``Command.show_default`` overrides this default for the + specific command. + + .. versionchanged:: 8.2 + The ``protected_args`` attribute is deprecated and will be removed in + Click 9.0. ``args`` will contain remaining unparsed tokens. + + .. versionchanged:: 8.1 + The ``show_default`` parameter is overridden by + ``Command.show_default``, instead of the other way around. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. + """ + + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: type[HelpFormatter] = HelpFormatter + + def __init__( + self, + command: Command, + parent: Context | None = None, + info_name: str | None = None, + obj: t.Any | None = None, + auto_envvar_prefix: str | None = None, + default_map: cabc.MutableMapping[str, t.Any] | None = None, + terminal_width: int | None = None, + max_content_width: int | None = None, + resilient_parsing: bool = False, + allow_extra_args: bool | None = None, + allow_interspersed_args: bool | None = None, + ignore_unknown_options: bool | None = None, + help_option_names: list[str] | None = None, + token_normalize_func: t.Callable[[str], str] | None = None, + color: bool | None = None, + show_default: bool | None = 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: dict[str, t.Any] = {} + #: the leftover arguments. + self.args: 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: list[str] = [] + #: the collected prefixes of the command's options. + self._opt_prefixes: set[str] = set(parent._opt_prefixes) if parent else set() + + if obj is None and parent is not None: + obj = parent.obj + + #: the user object stored. + self.obj: t.Any = obj + self._meta: 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: cabc.MutableMapping[str, t.Any] | None = 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: str | None = 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: int | None = 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: int | None = 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: 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.Callable[[str], str] | None = 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: str | None = 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: bool | None = 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: bool | None = show_default + + self._close_callbacks: list[t.Callable[[], t.Any]] = [] + self._depth = 0 + self._parameter_source: dict[str, ParameterSource] = {} + self._exit_stack = ExitStack() + + @property + def protected_args(self) -> list[str]: + import warnings + + warnings.warn( + "'protected_args' is deprecated and will be removed in Click 9.0." + " 'args' will contain remaining unparsed tokens.", + DeprecationWarning, + stacklevel=2, + ) + return self._protected_args + + def to_info_dict(self) -> 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: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> bool | None: + self._depth -= 1 + exit_result: bool | None = None + if self._depth == 0: + exit_result = self._close_with_exception_info(exc_type, exc_value, tb) + pop_context() + + return exit_result + + @contextmanager + def scope(self, cleanup: bool = True) -> cabc.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) -> 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: AbstractContextManager[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._close_with_exception_info(None, None, None) + + def _close_with_exception_info( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> bool | None: + """Unwind the exit stack by calling its :meth:`__exit__` providing the exception + information to allow for exception handling by the various resources registered + using :meth;`with_resource` + + :return: Whatever ``exit_stack.__exit__()`` returns. + """ + exit_result = self._exit_stack.__exit__(exc_type, exc_value, tb) + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() + + return exit_result + + @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: type[V]) -> V | None: + """Finds the closest object of a given type.""" + node: Context | None = 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: type[V]) -> V: + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + @t.overload + def lookup_default( + self, name: str, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def lookup_default( + self, name: str, call: t.Literal[False] = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def lookup_default(self, name: str, call: bool = True) -> t.Any | None: + """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, UNSET) + + if call and callable(value): + return value() + + return value + + return UNSET + + def fail(self, message: str) -> t.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) -> t.NoReturn: + """Aborts the script.""" + raise Abort() + + def exit(self, code: int = 0) -> t.NoReturn: + """Exits the application with a given exit code. + + .. versionchanged:: 8.2 + Callbacks and context managers registered with :meth:`call_on_close` + and :meth:`with_resource` are closed before exiting. + """ + self.close() + 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) + + @t.overload + def invoke( + self, callback: t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any + ) -> V: ... + + @t.overload + def invoke(self, callback: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: ... + + def invoke( + self, callback: Command | t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any + ) -> t.Any | V: + """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. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + + .. versionchanged:: 3.2 + A new context is created, and missing arguments use default values. + """ + 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 = t.cast("t.Callable[..., V]", 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) -> 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) -> ParameterSource | None: + """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 Command: + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed + :param hidden: hide this command from help outputs. + :param deprecated: If ``True`` or non-empty string, issues a message + indicating that the command is deprecated and highlights + its deprecation in --help. The message can be customized + by using a string as the value. + + .. versionchanged:: 8.2 + This is the base class for all commands, not ``BaseCommand``. + ``deprecated`` can be set to a string as well to customize the + deprecation message. + + .. versionchanged:: 8.1 + ``help``, ``epilog``, and ``short_help`` are stored unprocessed, + all formatting is done when outputting help text, not at init, + and is done even if not using the ``@command`` decorator. + + .. versionchanged:: 8.0 + Added a ``repr`` showing the command name. + + .. versionchanged:: 7.1 + Added the ``no_args_is_help`` parameter. + + .. versionchanged:: 2.0 + Added the ``context_settings`` parameter. + """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: 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: str | None, + context_settings: cabc.MutableMapping[str, t.Any] | None = None, + callback: t.Callable[..., t.Any] | None = None, + params: list[Parameter] | None = None, + help: str | None = None, + epilog: str | None = None, + short_help: str | None = None, + options_metavar: str | None = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool | str = False, + ) -> 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: cabc.MutableMapping[str, t.Any] = 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: list[Parameter] = params or [] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self._help_option = None + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + + def to_info_dict(self, ctx: Context) -> dict[str, t.Any]: + return { + "name": self.name, + "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, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + 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) -> list[Parameter]: + params = self.params + help_option = self.get_help_option(ctx) + + if help_option is not None: + params = [*params, help_option] + + if __debug__: + import warnings + + opts = [opt for param in params for opt in param.opts] + opts_counter = Counter(opts) + duplicate_opts = (opt for opt, count in opts_counter.items() if count > 1) + + for duplicate_opt in duplicate_opts: + warnings.warn( + ( + f"The parameter {duplicate_opt} is used more than once. " + "Remove its duplicate as parameters should be unique." + ), + stacklevel=3, + ) + + return params + + 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) -> 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) -> 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) -> Option | None: + """Returns the help option object. + + Skipped if :attr:`add_help_option` is ``False``. + + .. versionchanged:: 8.1.8 + The help option is now cached to avoid creating it multiple times. + """ + help_option_names = self.get_help_option_names(ctx) + + if not help_option_names or not self.add_help_option: + return None + + # Cache the help option object in private _help_option attribute to + # avoid creating it multiple times. Not doing this will break the + # callback odering by iter_params_for_processing(), which relies on + # object comparison. + if self._help_option is None: + # Avoid circular import. + from .decorators import help_option + + # Apply help_option decorator and pop resulting option + help_option(*help_option_names)(self) + self._help_option = self.params.pop() # type: ignore[assignment] + + return self._help_option + + def make_parser(self, ctx: Context) -> _OptionParser: + """Creates the underlying option parser for this command.""" + parser = _OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + if self.short_help: + text = inspect.cleandoc(self.short_help) + elif self.help: + text = make_default_short_help(self.help, limit) + else: + text = "" + + if self.deprecated: + deprecated_message = ( + f"(DEPRECATED: {self.deprecated})" + if isinstance(self.deprecated, str) + else "(DEPRECATED)" + ) + text = _("{text} {deprecated_message}").format( + text=text, deprecated_message=deprecated_message + ) + + 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.""" + if self.help is not None: + # truncate the help text to the first form feed + text = inspect.cleandoc(self.help).partition("\f")[0] + else: + text = "" + + if self.deprecated: + deprecated_message = ( + f"(DEPRECATED: {self.deprecated})" + if isinstance(self.deprecated, str) + else "(DEPRECATED)" + ) + text = _("{text} {deprecated_message}").format( + text=text, deprecated_message=deprecated_message + ) + + 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: + epilog = inspect.cleandoc(self.epilog) + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(epilog) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: Context | None = 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'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) + + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx: Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + raise NoArgsIsHelpError(ctx) + + 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)): + _, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) + + ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) + return args + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.deprecated: + extra_message = ( + f" {self.deprecated}" if isinstance(self.deprecated, str) else "" + ) + message = _( + "DeprecationWarning: The command {name!r} is deprecated.{extra_message}" + ).format(name=self.name, extra_message=extra_message) + 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) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. + + :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: 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) + ) + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, Group) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx._protected_args + ) + + return results + + @t.overload + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: t.Literal[True] = True, + **extra: t.Any, + ) -> t.NoReturn: ... + + @t.overload + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: ... + + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. + """ + if args is None: + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) + else: + args = list(args) + + if prog_name is None: + prog_name = _detect_program_name() + + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt) as e: + echo(file=sys.stderr) + raise Abort() from e + 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: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str | None = 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``. + + .. versionchanged:: 8.2.0 + Dots (``.``) in ``prog_name`` are replaced with underscores (``_``). + """ + if complete_var is None: + complete_name = prog_name.replace("-", "_").replace(".", "_") + complete_var = f"_{complete_name}_COMPLETE".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 _FakeSubclassCheck(type): + def __subclasscheck__(cls, subclass: type) -> bool: + return issubclass(subclass, cls.__bases__[0]) + + def __instancecheck__(cls, instance: t.Any) -> bool: + return isinstance(instance, cls.__bases__[0]) + + +class _BaseCommand(Command, metaclass=_FakeSubclassCheck): + """ + .. deprecated:: 8.2 + Will be removed in Click 9.0. Use ``Command`` instead. + """ + + +class Group(Command): + """A group is a command that nests other commands (or more groups). + + :param name: The name of the group command. + :param commands: Map names to :class:`Command` objects. Can be a list, which + will use :attr:`Command.name` as the keys. + :param invoke_without_command: Invoke the group's callback even if a + subcommand is not given. + :param no_args_is_help: If no arguments are given, show the group's help and + exit. Defaults to the opposite of ``invoke_without_command``. + :param subcommand_metavar: How to represent the subcommand argument in help. + The default will represent whether ``chain`` is set or not. + :param chain: Allow passing more than one subcommand argument. After parsing + a command's arguments, if any arguments remain another command will be + matched, and so on. + :param result_callback: A function to call after the group's and + subcommand's callbacks. The value returned by the subcommand is passed. + If ``chain`` is enabled, the value will be a list of values returned by + all the commands. If ``invoke_without_command`` is enabled, the value + will be the value returned by the group's callback, or an empty list if + ``chain`` is enabled. + :param kwargs: Other arguments passed to :class:`Command`. + + .. versionchanged:: 8.0 + The ``commands`` argument can be a list of command objects. + + .. versionchanged:: 8.2 + Merged with and replaces the ``MultiCommand`` base class. + """ + + allow_extra_args = True + allow_interspersed_args = False + + #: 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: type[Command] | None = 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: type[Group] | type[type] | None = None + # Literal[type] isn't valid, so use Type[type] + + def __init__( + self, + name: str | None = None, + commands: cabc.MutableMapping[str, Command] + | cabc.Sequence[Command] + | None = None, + invoke_without_command: bool = False, + no_args_is_help: bool | None = None, + subcommand_metavar: str | None = None, + chain: bool = False, + result_callback: t.Callable[..., t.Any] | None = None, + **kwargs: t.Any, + ) -> None: + super().__init__(name, **kwargs) + + 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: cabc.MutableMapping[str, Command] = commands + + 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( + "A group in chain mode cannot have optional arguments." + ) + + def to_info_dict(self, ctx: Context) -> 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 add_command(self, cmd: Command, name: str | None = 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_nested_chain(self, name, cmd, register=True) + self.commands[name] = cmd + + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: ... + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command] | Command: + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. + """ + from .decorators import command + + func: t.Callable[..., t.Any] | None = None + + if args and callable(args[0]): + assert len(args) == 1 and not kwargs, ( + "Use 'command(**kwargs)(callable)' to provide arguments." + ) + (func,) = args + args = () + + if self.command_class and kwargs.get("cls") is None: + kwargs["cls"] = self.command_class + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd: Command = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> Group: ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Group]: ... + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Group] | Group: + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. + """ + from .decorators import group + + func: t.Callable[..., t.Any] | None = None + + if args and callable(args[0]): + assert len(args) == 1 and not kwargs, ( + "Use 'group(**kwargs)(callable)' to provide arguments." + ) + (func,) = args + args = () + + if self.group_class is not None and kwargs.get("cls") is None: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> Group: + cmd: Group = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + def 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: t.Any, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + inner = old_callback(value, *args, **kwargs) + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) + return rv # type: ignore[return-value] + + return decorator + + def get_command(self, ctx: Context, cmd_name: str) -> Command | None: + """Given a context and a command name, this returns a :class:`Command` + object if it exists or returns ``None``. + """ + return self.commands.get(cmd_name) + + def list_commands(self, ctx: Context) -> list[str]: + """Returns a list of subcommand names in the order they should appear.""" + return sorted(self.commands) + + def collect_usage_pieces(self, ctx: Context) -> 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 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: list[str]) -> list[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + raise NoArgsIsHelpError(ctx) + + rest = super().parse_args(ctx, args) + + if self.chain: + ctx._protected_args = rest + ctx.args = [] + elif rest: + ctx._protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) + return value + + if not ctx._protected_args: + if self.invoke_without_command: + # No subcommand was invoked, so the result callback is + # invoked with the group return value for regular + # groups, or an empty list for chained groups. + with ctx: + rv = super().invoke(ctx) + return _process_result([] if self.chain else rv) + ctx.fail(_("Missing command.")) + + # Fetch args back out + args = [*ctx._protected_args, *ctx.args] + ctx.args = [] + ctx._protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + ctx.invoked_subcommand = cmd_name + super().invoke(ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command( + self, ctx: Context, args: list[str] + ) -> tuple[str | None, Command | None, 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, args) + ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) + return cmd_name if cmd else None, cmd, args[1:] + + def shell_complete(self, ctx: Context, incomplete: str) -> 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 _MultiCommand(Group, metaclass=_FakeSubclassCheck): + """ + .. deprecated:: 8.2 + Will be removed in Click 9.0. Use ``Group`` instead. + """ + + +class CommandCollection(Group): + """A :class:`Group` that looks up subcommands on other groups. If a command + is not found on this group, each registered source is checked in order. + Parameters on a source are not added to this group, and a source's callback + is not invoked when invoking its commands. In other words, this "flattens" + commands in many groups into this one group. + + :param name: The name of the group command. + :param sources: A list of :class:`Group` objects to look up commands from. + :param kwargs: Other arguments passed to :class:`Group`. + + .. versionchanged:: 8.2 + This is a subclass of ``Group``. Commands are looked up first on this + group, then each of its sources. + """ + + def __init__( + self, + name: str | None = None, + sources: list[Group] | None = None, + **kwargs: t.Any, + ) -> None: + super().__init__(name, **kwargs) + #: The list of registered groups. + self.sources: list[Group] = sources or [] + + def add_source(self, group: Group) -> None: + """Add a group as a source of commands.""" + self.sources.append(group) + + def get_command(self, ctx: Context, cmd_name: str) -> Command | None: + rv = super().get_command(ctx, cmd_name) + + if rv is not None: + return rv + + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + + if rv is not None: + if self.chain: + _check_nested_chain(self, cmd_name, rv) + + return rv + + return None + + def list_commands(self, ctx: Context) -> list[str]: + rv: set[str] = set(super().list_commands(ctx)) + + for source in self.sources: + rv.update(source.list_commands(ctx)) + + return sorted(rv) + + +def _check_iter(value: t.Any) -> cabc.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 latter 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: environment variable(s) that are used to provide a default value for + this parameter. This can be a string or a sequence of strings. If a sequence is + given, only the first non-empty environment variable is used for the parameter. + :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. + :param deprecated: If ``True`` or non-empty string, issues a message + indicating that the argument is deprecated and highlights + its deprecation in --help. The message can be customized + by using a string as the value. A deprecated parameter + cannot be required, a ValueError will be raised otherwise. + + .. versionchanged:: 8.2.0 + Introduction of ``deprecated``. + + .. versionchanged:: 8.2 + Adding duplicate parameter names to a :class:`~click.core.Command` will + result in a ``UserWarning`` being shown. + + .. versionchanged:: 8.2 + Adding duplicate parameter names to a :class:`~click.core.Command` will + result in a ``UserWarning`` being shown. + + .. 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: cabc.Sequence[str] | None = None, + type: types.ParamType | t.Any | None = None, + required: bool = False, + # XXX The default historically embed two concepts: + # - the declaration of a Parameter object carrying the default (handy to + # arbitrage the default value of coupled Parameters sharing the same + # self.name, like flag options), + # - and the actual value of the default. + # It is confusing and is the source of many issues discussed in: + # https://github.com/pallets/click/pull/3030 + # In the future, we might think of splitting it in two, not unlike + # Option.is_flag and Option.flag_value: we could have something like + # Parameter.is_default and Parameter.default_value. + default: t.Any | t.Callable[[], t.Any] | None = UNSET, + callback: t.Callable[[Context, Parameter, t.Any], t.Any] | None = None, + nargs: int | None = None, + multiple: bool = False, + metavar: str | None = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: str | cabc.Sequence[str] | None = None, + shell_complete: t.Callable[ + [Context, Parameter, str], list[CompletionItem] | list[str] + ] + | None = None, + deprecated: bool | str = False, + ) -> None: + self.name: str | None + self.opts: list[str] + self.secondary_opts: list[str] + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type: types.ParamType = types.convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = multiple + self.expose_value = expose_value + self.default = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self._custom_shell_complete = shell_complete + self.deprecated = deprecated + + 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}." + ) + + if required and deprecated: + raise ValueError( + f"The {self.param_type_name} '{self.human_readable_name}' " + "is deprecated and still required. A deprecated " + f"{self.param_type_name} cannot be required." + ) + + def to_info_dict(self) -> 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. + + .. versionchanged:: 8.3.0 + Returns ``None`` for the :attr:`default` if it was not set. + + .. 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, + # We explicitly hide the :attr:`UNSET` value to the user, as we choose to + # make it an implementation detail. And because ``to_info_dict`` has been + # designed for documentation purposes, we return ``None`` instead. + "default": self.default if self.default is not UNSET else None, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], 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, ctx: Context) -> str: + if self.metavar is not None: + return self.metavar + + metavar = self.type.get_metavar(param=self, ctx=ctx) + + if metavar is None: + metavar = self.type.name.upper() + + if self.nargs != 1: + metavar += "..." + + return metavar + + @t.overload + def get_default( + self, ctx: Context, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Any | t.Callable[[], t.Any] | None: + """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 UNSET: + 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: cabc.Mapping[str, t.Any] + ) -> tuple[t.Any, ParameterSource]: + """Returns the parameter value produced by the parser. + + If the parser did not produce a value from user input, the value is either + sourced from the environment variable, the default map, or the parameter's + default value. In that order of precedence. + + If no value is found, an internal sentinel value is returned. + + :meta private: + """ + # Collect from the parse the value passed by the user to the CLI. + value = opts.get(self.name, UNSET) # type: ignore + # If the value is set, it means it was sourced from the command line by the + # parser, otherwise it left unset by default. + source = ( + ParameterSource.COMMANDLINE + if value is not UNSET + else ParameterSource.DEFAULT + ) + + if value is UNSET: + envvar_value = self.value_from_envvar(ctx) + if envvar_value is not None: + value = envvar_value + source = ParameterSource.ENVIRONMENT + + if value is UNSET: + default_map_value = ctx.lookup_default(self.name) # type: ignore + if default_map_value is not UNSET: + value = default_map_value + source = ParameterSource.DEFAULT_MAP + + if value is UNSET: + default_value = self.get_default(ctx) + if default_value is not UNSET: + value = default_value + 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 parameter's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. + """ + if value in (None, UNSET): + if self.multiple or self.nargs == -1: + return () + else: + return value + + def check_iter(value: t.Any) -> cabc.Iterator[t.Any]: + 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 + + # Define the conversion function based on nargs and type. + + if self.nargs == 1 or self.type.is_composite: + + def convert(value: t.Any) -> t.Any: + return self.type(value, param=self, ctx=ctx) + + elif self.nargs == -1: + + def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] + 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: + """A value is considered missing if: + + - it is :attr:`UNSET`, + - or if it is an empty sequence while the parameter is suppose to have + non-single value (i.e. :attr:`nargs` is not ``1`` or :attr:`multiple` is + set). + + :meta private: + """ + if value is UNSET: + 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: + """Process the value of this parameter: + + 1. Type cast the value using :meth:`type_cast_value`. + 2. Check if the value is missing (see: :meth:`value_is_missing`), and raise + :exc:`MissingParameter` if it is required. + 3. If a :attr:`callback` is set, call it to have the value replaced by the + result of the callback. If the value was not set, the callback receive + ``None``. This keep the legacy behavior as it was before the introduction of + the :attr:`UNSET` sentinel. + + :meta private: + """ + 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: + # Legacy case: UNSET is not exposed directly to the callback, but converted + # to None. + if value is UNSET: + value = None + value = self.callback(ctx, self, value) + + return value + + def resolve_envvar_value(self, ctx: Context) -> str | None: + """Returns the value found in the environment variable(s) attached to this + parameter. + + Environment variables values are `always returned as strings + `_. + + This method returns ``None`` if: + + - the :attr:`envvar` property is not set on the :class:`Parameter`, + - the environment variable is not found in the environment, + - the variable is found in the environment but its value is empty (i.e. the + environment variable is present but has an empty string). + + If :attr:`envvar` is setup with multiple environment variables, + then only the first non-empty value is returned. + + .. caution:: + + The raw value extracted from the environment is not normalized and is + returned as-is. Any normalization or reconciliation is performed later by + the :class:`Parameter`'s :attr:`type`. + + :meta private: + """ + if not self.envvar: + 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) + + # Return the first non-empty value of the list of environment variables. + if rv: + return rv + # Else, absence of value is interpreted as an environment variable that + # is not set, so proceed to the next one. + + return None + + def value_from_envvar(self, ctx: Context) -> str | cabc.Sequence[str] | None: + """Process the raw environment variable string for this parameter. + + Returns the string as-is or splits it into a sequence of strings if the + parameter is expecting multiple values (i.e. its :attr:`nargs` property is set + to a value other than ``1``). + + :meta private: + """ + rv = self.resolve_envvar_value(ctx) + + if rv is not None and self.nargs != 1: + return self.type.split_envvar_value(rv) + + return rv + + def handle_parse_result( + self, ctx: Context, opts: cabc.Mapping[str, t.Any], args: list[str] + ) -> tuple[t.Any, list[str]]: + """Process the value produced by the parser from user input. + + Always process the value through the Parameter's :attr:`type`, wherever it + comes from. + + If the parameter is deprecated, this method warn the user about it. But only if + the value has been explicitly set by the user (and as such, is not coming from + a default). + + :meta private: + """ + with augment_usage_errors(ctx, param=self): + value, source = self.consume_value(ctx, opts) + + ctx.set_parameter_source(self.name, source) # type: ignore + + # Display a deprecation warning if necessary. + if ( + self.deprecated + and value is not UNSET + and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP) + ): + extra_message = ( + f" {self.deprecated}" if isinstance(self.deprecated, str) else "" + ) + message = _( + "DeprecationWarning: The {param_type} {name!r} is deprecated." + "{extra_message}" + ).format( + param_type=self.param_type_name, + name=self.human_readable_name, + extra_message=extra_message, + ) + echo(style(message, fg="red"), err=True) + + # Process the value through the parameter's type. + try: + value = self.process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + # In resilient parsing mode, we do not want to fail the command if the + # value is incompatible with the parameter type, so we reset the value + # to UNSET, which will be interpreted as a missing value. + value = UNSET + + # Add parameter's value to the context. + if ( + self.expose_value + # We skip adding the value if it was previously set by another parameter + # targeting the same variable name. This prevents parameters competing for + # the same name to override each other. + and self.name not in ctx.params + ): + # Click is logically enforcing that the name is None if the parameter is + # not to be exposed. We still assert it here to please the type checker. + assert self.name is not None, ( + f"{self!r} parameter's name should not be None when exposing value." + ) + # Normalize UNSET values to None, as we're about to pass them to the + # command function and move them to the pure-Python realm of user-written + # code. + ctx.params[self.name] = value if value is not UNSET else None + + return value, args + + def get_help_record(self, ctx: Context) -> tuple[str, str] | None: + pass + + def get_usage_pieces(self, ctx: Context) -> 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) -> 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("list[CompletionItem]", results) + + return self.type.shell_complete(ctx, self, incomplete) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: Show the default value for this option in its + help text. Values are not shown by default, unless + :attr:`Context.show_default` is ``True``. If this value is a + string, it shows that string in parentheses instead of the + actual value. This is particularly useful for dynamic options. + For single option boolean flags, the default remains hidden if + its value is ``False``. + :param show_envvar: Controls if an environment variable should be + shown on the help page and error messages. + 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. A deprecated option cannot be + prompted. + :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. + :param attrs: Other command arguments described in :class:`Parameter`. + + .. versionchanged:: 8.2 + ``envvar`` used with ``flag_value`` will always use the ``flag_value``, + previously it would use the value of the environment variable. + + .. versionchanged:: 8.1 + Help text indentation is cleaned here instead of only in the + ``@option`` decorator. + + .. versionchanged:: 8.1 + The ``show_default`` parameter overrides + ``Context.show_default``. + + .. versionchanged:: 8.1 + The default of a single option boolean flag is not shown if the + default value is ``False``. + + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls: cabc.Sequence[str] | None = None, + show_default: bool | str | None = None, + prompt: bool | str = False, + confirmation_prompt: bool | str = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: bool | None = None, + flag_value: t.Any = UNSET, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: types.ParamType | t.Any | None = None, + help: str | None = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + deprecated: bool | str = False, + **attrs: t.Any, + ) -> None: + if help: + help = inspect.cleandoc(help) + + super().__init__( + param_decls, type=type, multiple=multiple, deprecated=deprecated, **attrs + ) + + if prompt is True: + if self.name is None: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text: str | None = self.name.replace("_", " ").capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + + if deprecated: + deprecated_message = ( + f"(DEPRECATED: {deprecated})" + if isinstance(deprecated, str) + else "(DEPRECATED)" + ) + help = help + deprecated_message if help is not None else deprecated_message + + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required + self.hide_input = hide_input + self.hidden = hidden + + # The _flag_needs_value property tells the parser that this option is a flag + # that cannot be used standalone and needs a value. With this information, the + # parser can determine whether to consider the next user-provided argument in + # the CLI as a value for this flag or as a new option. + # If prompt is enabled but not required, then it opens the possibility for the + # option to gets its value from the user. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + + # Auto-detect if this is a flag or not. + if is_flag is None: + # Implicitly a flag because flag_value was set. + if flag_value is not UNSET: + is_flag = True + # Not a flag, but when used as a flag it shows a prompt. + elif self._flag_needs_value: + is_flag = False + # Implicitly a flag because secondary options names were given. + elif self.secondary_opts: + is_flag = True + # The option is explicitly not a flag. But we do not know yet if it needs a + # value or not. So we look at the default value to determine it. + elif is_flag is False and not self._flag_needs_value: + self._flag_needs_value = self.default is UNSET + + if is_flag: + # Set missing default for flags if not explicitly required or prompted. + if self.default is UNSET and not self.required and not self.prompt: + if multiple: + self.default = () + + # Auto-detect the type of the flag based on the flag_value. + if type is None: + # A flag without a flag_value is a boolean flag. + if flag_value is UNSET: + self.type = types.BoolParamType() + # If the flag value is a boolean, use BoolParamType. + elif isinstance(flag_value, bool): + self.type = types.BoolParamType() + # Otherwise, guess the type from the flag value. + else: + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = bool(is_flag) + self.is_bool_flag: bool = bool( + is_flag and isinstance(self.type, types.BoolParamType) + ) + self.flag_value: t.Any = flag_value + + # Set boolean flag default to False if unset and not required. + if self.is_bool_flag: + if self.default is UNSET and not self.required: + self.default = False + + # Support the special case of aligning the default value with the flag_value + # for flags whose default is explicitly set to True. Note that as long as we + # have this condition, there is no way a flag can have a default set to True, + # and a flag_value set to something else. Refs: + # https://github.com/pallets/click/issues/3024#issuecomment-3146199461 + # https://github.com/pallets/click/pull/3030/commits/06847da + if self.default is True and self.flag_value is not UNSET: + self.default = self.flag_value + + # Set the default flag_value if it is not set. + if self.flag_value is UNSET: + if self.is_flag: + self.flag_value = True + else: + self.flag_value = None + + # Counting. + self.count = count + if count: + if type is None: + self.type = types.IntRange(min=0) + if self.default is UNSET: + 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 deprecated and prompt: + raise ValueError("`deprecated` options cannot use `prompt`.") + + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + + 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) -> dict[str, t.Any]: + """ + .. versionchanged:: 8.3.0 + Returns ``None`` for the :attr:`flag_value` if it was not set. + """ + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + # We explicitly hide the :attr:`UNSET` value to the user, as we choose to + # make it an implementation detail. And because ``to_info_dict`` has been + # designed for documentation purposes, we return ``None`` instead. + flag_value=self.flag_value if self.flag_value is not UNSET else None, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def get_error_hint(self, ctx: Context) -> str: + result = super().get_error_hint(ctx) + if self.show_envvar and self.envvar is not None: + result += f" (env var: '{self.envvar}')" + return result + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], 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( + f"Could not determine name for option with declarations {decls!r}" + ) + + 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) -> tuple[str, str] | None: + if self.hidden: + return None + + any_prefix_is_slash = False + + def _write_opts(opts: cabc.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(ctx=ctx)}" + + return rv + + rv = [_write_opts(self.opts)] + + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + + extra = self.get_help_extra(ctx) + extra_items = [] + if "envvars" in extra: + extra_items.append( + _("env var: {var}").format(var=", ".join(extra["envvars"])) + ) + if "default" in extra: + extra_items.append(_("default: {default}").format(default=extra["default"])) + if "range" in extra: + extra_items.append(extra["range"]) + if "required" in extra: + extra_items.append(_(extra["required"])) + + if extra_items: + extra_str = "; ".join(extra_items) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + def get_help_extra(self, ctx: Context) -> types.OptionHelpExtra: + extra: types.OptionHelpExtra = {} + + 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: + if isinstance(envvar, str): + extra["envvars"] = (envvar,) + else: + extra["envvars"] = tuple(str(d) for d in envvar) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default = False + show_default_is_str = False + + if self.show_default is not None: + if isinstance(self.show_default, str): + show_default_is_str = show_default = True + else: + show_default = self.show_default + elif ctx.show_default is not None: + show_default = ctx.show_default + + if show_default_is_str or ( + show_default and (default_value not in (None, UNSET)) + ): + 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 isinstance(default_value, enum.Enum): + default_string = default_value.name + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = _split_opt( + (self.opts if default_value else self.secondary_opts)[0] + )[1] + elif self.is_bool_flag and not self.secondary_opts and not default_value: + default_string = "" + elif default_value == "": + default_string = '""' + else: + default_string = str(default_value) + + if default_string: + extra["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["range"] = range_str + + if self.required: + extra["required"] = "required" + + return extra + + 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 lock in the value before + # attempting any user interaction. + default = self.get_default(ctx) + + # A boolean flag can use a simplified [y/n] confirmation prompt. + if self.is_bool_flag: + # If we have no boolean default, we force the user to explicitly provide + # one. + if default in (UNSET, None): + default = None + # Nothing prevent you to declare an option that is simultaneously: + # 1) auto-detected as a boolean flag, + # 2) allowed to prompt, and + # 3) still declare a non-boolean default. + # This forced casting into a boolean is necessary to align any non-boolean + # default to the prompt, which is going to be a [y/n]-style confirmation + # because the option is still a boolean flag. That way, instead of [y/n], + # we get [Y/n] or [y/N] depending on the truthy value of the default. + # Refs: https://github.com/pallets/click/pull/3030#discussion_r2289180249 + else: + default = bool(default) + return confirm(self.prompt, default) + + # If show_default is set to True/False, provide this to `prompt` as well. For + # non-bool values of `show_default`, we use `prompt`'s default behavior + prompt_kwargs: t.Any = {} + if isinstance(self.show_default, bool): + prompt_kwargs["show_default"] = self.show_default + + return prompt( + self.prompt, + # Use ``None`` to inform the prompt() function to reiterate until a valid + # value is provided by the user if we have no default. + default=None if default is UNSET else 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), + **prompt_kwargs, + ) + + def resolve_envvar_value(self, ctx: Context) -> str | None: + """:class:`Option` resolves its environment variable the same way as + :func:`Parameter.resolve_envvar_value`, but it also supports + :attr:`Context.auto_envvar_prefix`. If we could not find an environment from + the :attr:`envvar` property, we fallback on :attr:`Context.auto_envvar_prefix` + to build dynamiccaly the environment variable name using the + :python:`{ctx.auto_envvar_prefix}_{self.name.upper()}` template. + + :meta private: + """ + rv = super().resolve_envvar_value(ctx) + + if rv is not None: + return rv + + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Any: + """For :class:`Option`, this method processes the raw environment variable + string the same way as :func:`Parameter.value_from_envvar` does. + + But in the case of non-boolean flags, the value is analyzed to determine if the + flag is activated or not, and returns a boolean of its activation, or the + :attr:`flag_value` if the latter is set. + + This method also takes care of repeated options (i.e. options with + :attr:`multiple` set to ``True``). + + :meta private: + """ + rv = self.resolve_envvar_value(ctx) + + # Absent environment variable or an empty string is interpreted as unset. + if rv is None: + return None + + # Non-boolean flags are more liberal in what they accept. But a flag being a + # flag, its envvar value still needs to be analyzed to determine if the flag is + # activated or not. + if self.is_flag and not self.is_bool_flag: + # If the flag_value is set and match the envvar value, return it + # directly. + if self.flag_value is not UNSET and rv == self.flag_value: + return self.flag_value + # Analyze the envvar value as a boolean to know if the flag is + # activated or not. + return types.BoolParamType.str_to_bool(rv) + + # Split the envvar value if it is allowed to be repeated. + value_depth = (self.nargs != 1) + bool(self.multiple) + if value_depth > 0: + multi_rv = self.type.split_envvar_value(rv) + if self.multiple and self.nargs != 1: + multi_rv = batch(multi_rv, self.nargs) # type: ignore[assignment] + + return multi_rv + + return rv + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, Parameter] + ) -> tuple[t.Any, ParameterSource]: + """For :class:`Option`, the value can be collected from an interactive prompt + if the option is a flag that needs a value (and the :attr:`prompt` property is + set). + + Additionally, this method handles flag option that are activated without a + value, in which case the :attr:`flag_value` is returned. + + :meta private: + """ + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option is allowed to as a flag + # without a value. + if value is FLAG_NEEDS_VALUE: + # If the option allows for a prompt, we start an interaction with the user. + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + # Else the flag takes its flag_value as value. + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + # A flag which is activated always returns the flag value, unless the value + # comes from the explicitly sets default. + elif ( + self.is_flag + and value is True + and not self.is_bool_flag + and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP) + ): + value = self.flag_value + + # Re-interpret a multiple option which has been sent as-is by the parser. + # Here we replace each occurrence of value-less flags (marked by the + # FLAG_NEEDS_VALUE sentinel) with the flag_value. + elif ( + self.multiple + and value is not UNSET + and source not in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP) + 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 for one to the user + # if prompting is enabled. + elif ( + ( + value is UNSET + or source in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP) + ) + 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 + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + if self.is_flag and not self.required: + if value is UNSET: + if self.is_bool_flag: + # If the flag is a boolean flag, we return False if it is not set. + value = False + return super().type_cast_value(ctx, value) + + +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 constructor of :class:`Parameter`. + """ + + param_type_name = "argument" + + def __init__( + self, + param_decls: cabc.Sequence[str], + required: bool | None = None, + **attrs: t.Any, + ) -> None: + # Auto-detect the requirement status of the argument if not explicitly set. + if required is None: + # The argument gets automatically required if it has no explicit default + # value set and is setup to match at least one value. + if attrs.get("default", UNSET) is UNSET: + required = attrs.get("nargs", 1) > 0 + # If the argument has a default value, it is not required. + else: + required = False + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + @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, ctx: Context) -> str: + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(param=self, ctx=ctx) + if not var: + var = self.name.upper() # type: ignore + if self.deprecated: + var += "!" + if not self.required: + var = f"[{var}]" + if self.nargs != 1: + var += "..." + return var + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], list[str]]: + if not decls: + if not expose_value: + return None, [], [] + raise TypeError("Argument is marked as exposed, but does not have a name.") + 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)}: {decls}." + ) + return name, [arg], [] + + def get_usage_pieces(self, ctx: Context) -> list[str]: + return [self.make_metavar(ctx)] + + def get_error_hint(self, ctx: Context) -> str: + return f"'{self.make_metavar(ctx)}'" + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) + + +def __getattr__(name: str) -> object: + import warnings + + if name == "BaseCommand": + warnings.warn( + "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Command' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _BaseCommand + + if name == "MultiCommand": + warnings.warn( + "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Group' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _MultiCommand + + raise AttributeError(name) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/decorators.py b/web_viewer/.venv/lib/python3.12/site-packages/click/decorators.py new file mode 100644 index 0000000..21f4c34 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/decorators.py @@ -0,0 +1,551 @@ +from __future__ import annotations + +import inspect +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 + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") +T = t.TypeVar("T") +_AnyCallable = t.Callable[..., t.Any] +FC = t.TypeVar("FC", bound="_AnyCallable | Command") + + +def pass_context(f: t.Callable[te.Concatenate[Context, P], R]) -> t.Callable[P, R]: + """Marks a callback as wanting to receive the current context + object as first argument. + """ + + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + return f(get_current_context(), *args, **kwargs) + + return update_wrapper(new_func, f) + + +def pass_obj(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + """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: P.args, **kwargs: P.kwargs) -> R: + return f(get_current_context().obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + +def make_pass_decorator( + object_type: type[T], ensure: bool = False +) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: + """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: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = get_current_context() + + obj: T | None + 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(new_func, f) + + return decorator + + +def pass_meta_key( + key: str, *, doc_description: str | None = None +) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: + """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: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator + + +CmdType = t.TypeVar("CmdType", bound=Command) + + +# variant: no call, directly as decorator for a function. +@t.overload +def command(name: _AnyCallable) -> Command: ... + + +# variant: with positional name and with positional or keyword cls argument: +# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...) +@t.overload +def command( + name: str | None, + cls: type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: ... + + +# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) +@t.overload +def command( + name: None = None, + *, + cls: type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def command( + name: str | None = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Command]: ... + + +def command( + name: str | _AnyCallable | None = None, + cls: type[CmdType] | None = None, + **attrs: t.Any, +) -> Command | t.Callable[[_AnyCallable], Command | CmdType]: + 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, converted to + lowercase, with underscores ``_`` replaced by dashes ``-``, and the suffixes + ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed. For example, + ``init_data_command`` becomes ``init-data``. + + All keyword arguments are forwarded to the underlying command class. + For the ``params`` argument, any decorated params are appended to + the end of the list. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: The name of the command. Defaults to modifying the function's + name as described above. + :param cls: The command class to create. Defaults to :class:`Command`. + + .. versionchanged:: 8.2 + The suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are + removed when generating the name. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.1 + The ``params`` argument can be used. Decorated params are + appended to the end of the list. + """ + + func: t.Callable[[_AnyCallable], t.Any] | None = None + + if callable(name): + func = name + name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + + if cls is None: + cls = t.cast("type[CmdType]", Command) + + def decorator(f: _AnyCallable) -> CmdType: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + attr_params = attrs.pop("params", None) + params = attr_params if attr_params is not None else [] + + try: + decorator_params = f.__click_params__ # type: ignore + except AttributeError: + pass + else: + del f.__click_params__ # type: ignore + params.extend(reversed(decorator_params)) + + if attrs.get("help") is None: + attrs["help"] = f.__doc__ + + if t.TYPE_CHECKING: + assert cls is not None + assert not callable(name) + + if name is not None: + cmd_name = name + else: + cmd_name = f.__name__.lower().replace("_", "-") + cmd_left, sep, suffix = cmd_name.rpartition("-") + + if sep and suffix in {"command", "cmd", "group", "grp"}: + cmd_name = cmd_left + + cmd = cls(name=cmd_name, callback=f, params=params, **attrs) + cmd.__doc__ = f.__doc__ + return cmd + + if func is not None: + return decorator(func) + + return decorator + + +GrpType = t.TypeVar("GrpType", bound=Group) + + +# variant: no call, directly as decorator for a function. +@t.overload +def group(name: _AnyCallable) -> Group: ... + + +# variant: with positional name and with positional or keyword cls argument: +# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...) +@t.overload +def group( + name: str | None, + cls: type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: ... + + +# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) +@t.overload +def group( + name: None = None, + *, + cls: type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def group( + name: str | None = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Group]: ... + + +def group( + name: str | _AnyCallable | None = None, + cls: type[GrpType] | None = None, + **attrs: t.Any, +) -> Group | t.Callable[[_AnyCallable], Group | GrpType]: + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + """ + if cls is None: + cls = t.cast("type[GrpType]", Group) + + if callable(name): + return command(cls=cls, **attrs)(name) + + return command(name, cls, **attrs) + + +def _param_memo(f: t.Callable[..., t.Any], 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, cls: type[Argument] | None = None, **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. + + For the default argument class, refer to :class:`Argument` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Argument + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def option( + *param_decls: str, cls: type[Option] | None = None, **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. + + For the default option class, refer to :class:`Option` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Option + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **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: str | None = None, + *param_decls: str, + package_name: str | None = None, + prog_name: str | None = None, + message: str | None = 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``. + + 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: + import importlib.metadata + + try: + version = importlib.metadata.version(package_name) + except importlib.metadata.PackageNotFoundError: + 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( + 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]: + """Pre-configured ``--help`` option which immediately prints the help page + and exits the program. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def show_help(ctx: Context, param: Parameter, value: bool) -> None: + """Callback that print the help page on ```` and exits.""" + if value and not ctx.resilient_parsing: + 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.setdefault("callback", show_help) + + return option(*param_decls, **kwargs) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/exceptions.py b/web_viewer/.venv/lib/python3.12/site-packages/click/exceptions.py new file mode 100644 index 0000000..4d782ee --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/exceptions.py @@ -0,0 +1,308 @@ +from __future__ import annotations + +import collections.abc as cabc +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr +from .globals import resolve_color_default +from .utils import echo +from .utils import format_filename + +if t.TYPE_CHECKING: + from .core import Command + from .core import Context + from .core import Parameter + + +def _join_param_hints(param_hint: cabc.Sequence[str] | str | None) -> str | None: + 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) + # The context will be removed by the time we print the message, so cache + # the color settings here to be used later on (in `show`) + self.show_color: bool | None = resolve_color_default() + self.message = message + + def format_message(self) -> str: + return self.message + + def __str__(self) -> str: + return self.message + + def show(self, file: t.IO[t.Any] | None = None) -> None: + if file is None: + file = get_text_stderr() + + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=self.show_color, + ) + + +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: Context | None = None) -> None: + super().__init__(message) + self.ctx = ctx + self.cmd: Command | None = self.ctx.command if self.ctx else None + + def show(self, file: t.IO[t.Any] | None = 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: Context | None = None, + param: Parameter | None = None, + param_hint: cabc.Sequence[str] | str | None = 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: str | None = None, + ctx: Context | None = None, + param: Parameter | None = None, + param_hint: cabc.Sequence[str] | str | None = None, + param_type: str | None = 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: cabc.Sequence[str] | str | None = 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( + param=self.param, ctx=self.ctx + ) + 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: str | None = None, + possibilities: cabc.Sequence[str] | None = None, + ctx: Context | None = 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: Context | None = 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 NoArgsIsHelpError(UsageError): + def __init__(self, ctx: Context) -> None: + self.ctx: Context + super().__init__(ctx.get_help(), ctx=ctx) + + def show(self, file: t.IO[t.Any] | None = None) -> None: + echo(self.format_message(), file=file, err=True, color=self.ctx.color) + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename: str, hint: str | None = None) -> None: + if hint is None: + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename: str = format_filename(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: int = code diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/formatting.py b/web_viewer/.venv/lib/python3.12/site-packages/click/formatting.py new file mode 100644 index 0000000..0b64f83 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/formatting.py @@ -0,0 +1,301 @@ +from __future__ import annotations + +import collections.abc as cabc +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: int | None = None + + +def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]: + widths: 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: cabc.Iterable[tuple[str, str]], col_count: int +) -> cabc.Iterator[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: list[tuple[int, bool, str]] = [] + buf: 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: int | None = None, + max_width: int | None = None, + ) -> None: + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + import shutil + + 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: int = 0 + self.buffer: 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: str | None = 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: cabc.Sequence[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) -> cabc.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) -> cabc.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: cabc.Sequence[str]) -> 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/web_viewer/.venv/lib/python3.12/site-packages/click/globals.py b/web_viewer/.venv/lib/python3.12/site-packages/click/globals.py new file mode 100644 index 0000000..a2f9172 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/globals.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import typing as t +from threading import local + +if t.TYPE_CHECKING: + from .core import Context + +_local = local() + + +@t.overload +def get_current_context(silent: t.Literal[False] = False) -> Context: ... + + +@t.overload +def get_current_context(silent: bool = ...) -> Context | None: ... + + +def get_current_context(silent: bool = False) -> Context | None: + """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: bool | None = None) -> bool | None: + """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/web_viewer/.venv/lib/python3.12/site-packages/click/parser.py b/web_viewer/.venv/lib/python3.12/site-packages/click/parser.py new file mode 100644 index 0000000..1ea1f71 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/parser.py @@ -0,0 +1,532 @@ +""" +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 +from __future__ import annotations + +import collections.abc as cabc +import typing as t +from collections import deque +from gettext import gettext as _ +from gettext import ngettext + +from ._utils import FLAG_NEEDS_VALUE +from ._utils import UNSET +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + from ._utils import T_FLAG_NEEDS_VALUE + from ._utils import T_UNSET + 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") + + +def _unpack_args( + args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int] +) -> tuple[cabc.Sequence[str | cabc.Sequence[str | None] | None], 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 ``UNSET``. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv: list[str | tuple[str | T_UNSET, ...] | T_UNSET] = [] + spos: int | None = None + + def _fetch(c: deque[V]) -> V | T_UNSET: + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return UNSET + + while nargs_spec: + nargs = _fetch(nargs_spec) + + if nargs is None: + continue + + if nargs == 1: + rv.append(_fetch(args)) # type: ignore[arg-type] + 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(UNSET) + + # 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) -> 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: Context | None) -> 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)}" + + +class _Option: + def __init__( + self, + obj: CoreOption, + opts: cabc.Sequence[str], + dest: str | None, + action: str | None = None, + nargs: int = 1, + const: t.Any | None = None, + ): + self._short_opts = [] + self._long_opts = [] + self.prefixes: set[str] = 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: t.Any, 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: str | None, nargs: int = 1): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process( + self, + value: str | cabc.Sequence[str | None] | None | T_UNSET, + state: _ParsingState, + ) -> None: + if self.nargs > 1: + assert isinstance(value, cabc.Sequence) + holes = sum(1 for x in value if x is UNSET) + if holes == len(value): + value = UNSET + elif holes != 0: + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + # We failed to collect any argument value so we consider the argument as unset. + if value == (): + value = UNSET + + state.opts[self.dest] = value # type: ignore + state.order.append(self.obj) + + +class _ParsingState: + def __init__(self, rargs: list[str]) -> None: + self.opts: dict[str, t.Any] = {} + self.largs: list[str] = [] + self.rargs = rargs + self.order: 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. + + .. deprecated:: 8.2 + Will be removed in Click 9.0. + """ + + def __init__(self, ctx: Context | None = 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: bool = 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: bool = 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: dict[str, _Option] = {} + self._long_opt: dict[str, _Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: list[_Argument] = [] + + def add_option( + self, + obj: CoreOption, + opts: cabc.Sequence[str], + dest: str | None, + action: str | None = None, + nargs: int = 1, + const: t.Any | None = 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: str | None, 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: list[str] + ) -> tuple[dict[str, t.Any], list[str], 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: str | None, 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 = UNSET + + 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 = UNSET + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we recombine 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 + ) -> str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE: + nargs = option.nargs + + value: str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE + + 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) + + +def __getattr__(name: str) -> object: + import warnings + + if name in { + "OptionParser", + "Argument", + "Option", + "split_opt", + "normalize_opt", + "ParsingState", + }: + warnings.warn( + f"'parser.{name}' is deprecated and will be removed in Click 9.0." + " The old parser is available in 'optparse'.", + DeprecationWarning, + stacklevel=2, + ) + return globals()[f"_{name}"] + + if name == "split_arg_string": + from .shell_completion import split_arg_string + + warnings.warn( + "Importing 'parser.split_arg_string' is deprecated, it will only be" + " available in 'shell_completion' in Click 9.0.", + DeprecationWarning, + stacklevel=2, + ) + return split_arg_string + + raise AttributeError(name) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/py.typed b/web_viewer/.venv/lib/python3.12/site-packages/click/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/shell_completion.py b/web_viewer/.venv/lib/python3.12/site-packages/click/shell_completion.py new file mode 100644 index 0000000..8f1564c --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/shell_completion.py @@ -0,0 +1,667 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import re +import typing as t +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 .core import ParameterSource +from .utils import echo + + +def shell_complete( + cli: Command, + ctx_args: cabc.MutableMapping[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: str | None = None, + **kwargs: t.Any, + ) -> None: + self.value: t.Any = value + self.type: str = type + self.help: str | None = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMPREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMPREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +# See ZshComplete.format_completion below, and issue #2703, before +# changing this script. +# +# (TL;DR: _describe is picky about the format, but this Zsh script snippet +# is already widely deployed. So freeze this script, and use clever-ish +# handling of colons in ZshComplet.format_completion.) +_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 +} + +if [[ $zsh_eval_context[-1] == loadautofunc ]]; then + # autoload from fpath, call function directly + %(complete_func)s "$@" +else + # eval/source/. command, register function for later + compdef %(complete_func)s %(prog_name)s +fi +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + + 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: Command, + ctx_args: cabc.MutableMapping[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("-", "_"), flags=re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> 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) -> tuple[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: list[str], incomplete: str) -> 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 + + @staticmethod + def _check_version() -> None: + import shutil + import subprocess + + bash_exe = shutil.which("bash") + + if bash_exe is None: + match = None + else: + output = subprocess.run( + [bash_exe, "--norc", "-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": + echo( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ), + err=True, + ) + else: + echo( + _("Couldn't detect Bash version, shell completion is not supported."), + err=True, + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> tuple[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) -> tuple[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: + help_ = item.help or "_" + # The zsh completion script uses `_describe` on items with help + # texts (which splits the item help from the item value at the + # first unescaped colon) and `compadd` on items without help + # text (which uses the item value as-is and does not support + # colon escaping). So escape colons in the item value if and + # only if the item help is not the sentinel "_" value, as used + # by the completion script. + # + # (The zsh completion script is potentially widely deployed, and + # thus harder to fix than this method.) + # + # See issue #1812 and issue #2703 for further context. + value = item.value.replace(":", r"\:") if help_ != "_" else item.value + return f"{item.type}\n{value}\n{help_}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + if incomplete: + incomplete = split_arg_string(incomplete)[0] + 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}" + + +ShellCompleteType = t.TypeVar("ShellCompleteType", bound="type[ShellComplete]") + + +_available_shells: dict[str, type[ShellComplete]] = { + "bash": BashComplete, + "fish": FishComplete, + "zsh": ZshComplete, +} + + +def add_completion_class( + cls: ShellCompleteType, name: str | None = None +) -> ShellCompleteType: + """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 + + return cls + + +def get_completion_class(shell: str) -> type[ShellComplete] | None: + """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 split_arg_string(string: str) -> 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. + + .. versionchanged:: 8.2 + Moved to ``shell_completion`` from ``parser``. + """ + 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 + + +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 + # Will be None if expose_value is False. + value = ctx.params.get(param.name) + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(ctx: Context, value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + return c in ctx._opt_prefixes + + +def _is_incomplete_option(ctx: Context, args: list[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag or param.count: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(ctx, arg): + last_option = arg + break + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + args: 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 + with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx: + args = ctx._protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, Group): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + with cmd.make_context( + name, args, parent=ctx, resilient_parsing=True + ) as sub_ctx: + ctx = sub_ctx + args = ctx._protected_args + ctx.args + else: + sub_ctx = ctx + + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + with cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) as sub_sub_ctx: + sub_ctx = sub_sub_ctx + 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: list[str], incomplete: str +) -> tuple[Command | Parameter, str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(ctx, incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(ctx, incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(ctx, args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/termui.py b/web_viewer/.venv/lib/python3.12/site-packages/click/termui.py new file mode 100644 index 0000000..dcbb222 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/termui.py @@ -0,0 +1,877 @@ +from __future__ import annotations + +import collections.abc as cabc +import inspect +import io +import itertools +import sys +import typing as t +from contextlib import AbstractContextManager +from gettext import gettext as _ + +from ._compat import isatty +from ._compat import strip_ansi +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.Any | None = None, + show_choices: bool = True, + type: ParamType | None = 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 + + return default + + +def prompt( + text: str, + default: t.Any | None = None, + hide_input: bool = False, + confirmation_prompt: bool | str = False, + type: ParamType | t.Any | None = None, + value_proc: t.Callable[[str], t.Any] | None = None, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, + show_choices: bool = True, +) -> t.Any: + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending an interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + return f(" ") + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() from None + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) + + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: + value = prompt_func(prompt) + if value: + break + elif default is not None: + value = default + break + try: + result = value_proc(value) + except UsageError as e: + if hide_input: + echo(_("Error: The value you entered was invalid."), err=err) + else: + echo(_("Error: {e.message}").format(e=e), err=err) + continue + if not confirmation_prompt: + return result + while True: + value2 = prompt_func(confirmation_prompt) + is_empty = not value and not value2 + if value2 or is_empty: + break + if value == value2: + return result + echo(_("Error: The two entered values do not match."), err=err) + + +def confirm( + text: str, + default: bool | None = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the question to ask. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. + """ + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + value = visible_prompt_func(" ").lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() from None + if value in ("y", "yes"): + rv = True + elif value in ("n", "no"): + rv = False + elif default is not None and value == "": + rv = default + else: + echo(_("Error: invalid input"), err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def echo_via_pager( + text_or_generator: cabc.Iterable[str] | t.Callable[[], cabc.Iterable[str]] | str, + color: bool | None = 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[[], cabc.Iterable[str]]", text_or_generator)() + elif isinstance(text_or_generator, str): + i = [text_or_generator] + else: + i = iter(t.cast("cabc.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) + + +@t.overload +def progressbar( + *, + length: int, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[int]: ... + + +@t.overload +def progressbar( + iterable: cabc.Iterable[V] | None = None, + length: int | None = None, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[V]: ... + + +def progressbar( + iterable: cabc.Iterable[V] | None = None, + length: int | None = None, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = 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 hidden: hide the progressbar. Defaults to ``False``. When no tty is + detected, it will only print the progressbar label. Setting this to + ``False`` also disables that. + :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. + + .. versionadded:: 8.2 + The ``hidden`` argument. + + .. 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 + The ``update_min_steps`` parameter. + + .. versionadded:: 4.0 + The ``color`` parameter and ``update`` method. + + .. versionadded:: 2.0 + """ + from ._termui_impl import ProgressBar + + color = resolve_color_default(color) + return ProgressBar( + iterable=iterable, + length=length, + hidden=hidden, + 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 + + # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor + echo("\033[2J\033[1;1H", nl=False) + + +def _interpret_color(color: int | 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: int | tuple[int, int, int] | str | None = None, + bg: int | tuple[int, int, int] | str | None = None, + bold: bool | None = None, + dim: bool | None = None, + underline: bool | None = None, + overline: bool | None = None, + italic: bool | None = None, + blink: bool | None = None, + reverse: bool | None = None, + strikethrough: bool | None = 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.Any | None = None, + file: t.IO[t.AnyStr] | None = None, + nl: bool = True, + err: bool = False, + color: bool | None = 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) + + +@t.overload +def edit( + text: bytes | bytearray, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = False, + extension: str = ".txt", +) -> bytes | None: ... + + +@t.overload +def edit( + text: str, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", +) -> str | None: ... + + +@t.overload +def edit( + text: None = None, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + filename: str | cabc.Iterable[str] | None = None, +) -> None: ... + + +def edit( + text: str | bytes | bytearray | None = None, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + filename: str | cabc.Iterable[str] | None = None, +) -> str | bytes | bytearray | None: + 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. If the editor supports + editing multiple files at once, a sequence of files may be + passed as well. Invoke `click.file` once per file instead + if multiple files cannot be managed at once or editing the + files serially is desired. + + .. versionchanged:: 8.2.0 + ``filename`` now accepts any ``Iterable[str]`` in addition to a ``str`` + if the ``editor`` supports editing multiple files at once. + + """ + 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) + + if isinstance(filename, str): + filename = (filename,) + + ed.edit_files(filenames=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.Callable[[bool], str] | None = 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() -> AbstractContextManager[int]: + from ._termui_impl import raw_terminal as f + + return f() + + +def pause(info: str | None = 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/web_viewer/.venv/lib/python3.12/site-packages/click/testing.py b/web_viewer/.venv/lib/python3.12/site-packages/click/testing.py new file mode 100644 index 0000000..f6f60b8 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/testing.py @@ -0,0 +1,577 @@ +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import io +import os +import shlex +import sys +import tempfile +import typing as t +from types import TracebackType + +from . import _compat +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from _typeshed import ReadableBuffer + + from .core import Command + + +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) -> list[bytes]: + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self) -> cabc.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: EchoingStdin | None) -> cabc.Iterator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class BytesIOCopy(io.BytesIO): + """Patch ``io.BytesIO`` to let the written stream be copied to another. + + .. versionadded:: 8.2 + """ + + def __init__(self, copy_to: io.BytesIO) -> None: + super().__init__() + self.copy_to = copy_to + + def flush(self) -> None: + super().flush() + self.copy_to.flush() + + def write(self, b: ReadableBuffer) -> int: + self.copy_to.write(b) + return super().write(b) + + +class StreamMixer: + """Mixes `` and `` streams. + + The result is available in the ``output`` attribute. + + .. versionadded:: 8.2 + """ + + def __init__(self) -> None: + self.output: io.BytesIO = io.BytesIO() + self.stdout: io.BytesIO = BytesIOCopy(copy_to=self.output) + self.stderr: io.BytesIO = BytesIOCopy(copy_to=self.output) + + def __del__(self) -> None: + """ + Guarantee that embedded file-like objects are closed in a + predictable order, protecting against races between + self.output being closed and other streams being flushed on close + + .. versionadded:: 8.2.2 + """ + self.stderr.close() + self.stdout.close() + self.output.close() + + +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: str | bytes | t.IO[t.Any] | None, charset: str +) -> t.BinaryIO: + # Is already an input stream. + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast("t.IO[t.Any]", 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(input) + + +class Result: + """Holds the captured result of an invoked CLI script. + + :param runner: The runner that created the result + :param stdout_bytes: The standard output as bytes. + :param stderr_bytes: The standard error as bytes. + :param output_bytes: A mix of ``stdout_bytes`` and ``stderr_bytes``, as the + user would see it in its terminal. + :param return_value: The value returned from the invoked command. + :param exit_code: The exit code as integer. + :param exception: The exception that happened if one did. + :param exc_info: Exception information (exception type, exception instance, + traceback type). + + .. versionchanged:: 8.2 + ``stderr_bytes`` no longer optional, ``output_bytes`` introduced and + ``mix_stderr`` has been removed. + + .. versionadded:: 8.0 + Added ``return_value``. + """ + + def __init__( + self, + runner: CliRunner, + stdout_bytes: bytes, + stderr_bytes: bytes, + output_bytes: bytes, + return_value: t.Any, + exit_code: int, + exception: BaseException | None, + exc_info: tuple[type[BaseException], BaseException, TracebackType] + | None = None, + ): + self.runner = runner + self.stdout_bytes = stdout_bytes + self.stderr_bytes = stderr_bytes + self.output_bytes = output_bytes + self.return_value = return_value + self.exit_code = exit_code + self.exception = exception + self.exc_info = exc_info + + @property + def output(self) -> str: + """The terminal output as unicode string, as the user would see it. + + .. versionchanged:: 8.2 + No longer a proxy for ``self.stdout``. Now has its own independent stream + that is mixing `` and ``, in the order they were written. + """ + return self.output_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @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. + + .. versionchanged:: 8.2 + No longer raise an exception, always returns the `` string. + """ + 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 `` writes + to ``. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param catch_exceptions: Whether to catch any exceptions other than + ``SystemExit`` when running :meth:`~CliRunner.invoke`. + + .. versionchanged:: 8.2 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 8.2 + ``mix_stderr`` parameter has been removed. + """ + + def __init__( + self, + charset: str = "utf-8", + env: cabc.Mapping[str, str | None] | None = None, + echo_stdin: bool = False, + catch_exceptions: bool = True, + ) -> None: + self.charset = charset + self.env: cabc.Mapping[str, str | None] = env or {} + self.echo_stdin = echo_stdin + self.catch_exceptions = catch_exceptions + + def get_default_prog_name(self, cli: Command) -> 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: cabc.Mapping[str, str | None] | None = None + ) -> cabc.Mapping[str, str | None]: + """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: str | bytes | t.IO[t.Any] | None = None, + env: cabc.Mapping[str, str | None] | None = None, + color: bool = False, + ) -> cabc.Iterator[tuple[io.BytesIO, io.BytesIO, io.BytesIO]]: + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up `` 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. + + .. versionadded:: 8.2 + An additional output stream is returned, which is a mix of + `` and `` streams. + + .. versionchanged:: 8.2 + Always returns the `` stream. + + .. versionchanged:: 8.0 + `` 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) + + stream_mixer = StreamMixer() + + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, stream_mixer.stdout) + ) + + 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( + stream_mixer.stdout, encoding=self.charset, name="", mode="w" + ) + + sys.stderr = _NamedTextIOWrapper( + stream_mixer.stderr, + encoding=self.charset, + name="", + mode="w", + errors="backslashreplace", + ) + + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: str | None = None) -> str: + sys.stdout.write(prompt or "") + try: + val = next(text_input).rstrip("\r\n") + except StopIteration as e: + raise EOFError() from e + sys.stdout.write(f"{val}\n") + sys.stdout.flush() + return val + + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: str | None = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") + sys.stdout.flush() + try: + return next(text_input).rstrip("\r\n") + except StopIteration as e: + raise EOFError() from e + + @_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.IO[t.Any] | None = None, color: bool | None = 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 + old__compat_should_strip_ansi = _compat.should_strip_ansi + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore + _compat.should_strip_ansi = should_strip_ansi + + 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 (stream_mixer.stdout, stream_mixer.stderr, stream_mixer.output) + 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 + _compat.should_strip_ansi = old__compat_should_strip_ansi + formatting.FORCED_WIDTH = old_forced_width + + def invoke( + self, + cli: Command, + args: str | cabc.Sequence[str] | None = None, + input: str | bytes | t.IO[t.Any] | None = None, + env: cabc.Mapping[str, str | None] | None = None, + catch_exceptions: bool | None = None, + 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``. If :data:`None`, the value + from :class:`CliRunner` is used. + :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. + + .. versionadded:: 8.2 + The result object has the ``output_bytes`` attribute with + the mix of ``stdout_bytes`` and ``stderr_bytes``, as the user would + see it in its terminal. + + .. versionchanged:: 8.2 + The result object always returns the ``stderr_bytes`` stream. + + .. 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 + if catch_exceptions is None: + catch_exceptions = self.catch_exceptions + + with self.isolation(input=input, env=env, color=color) as outstreams: + return_value = None + exception: BaseException | None = 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("int | t.Any | None", 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() + sys.stderr.flush() + stdout = outstreams[0].getvalue() + stderr = outstreams[1].getvalue() + output = outstreams[2].getvalue() + + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + output_bytes=output, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) + + @contextlib.contextmanager + def isolated_filesystem( + self, temp_dir: str | os.PathLike[str] | None = None + ) -> cabc.Iterator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. + """ + cwd = os.getcwd() + dt = tempfile.mkdtemp(dir=temp_dir) + os.chdir(dt) + + try: + yield dt + finally: + os.chdir(cwd) + + if temp_dir is None: + import shutil + + try: + shutil.rmtree(dt) + except OSError: + pass diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/types.py b/web_viewer/.venv/lib/python3.12/site-packages/click/types.py new file mode 100644 index 0000000..e71c1c2 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/types.py @@ -0,0 +1,1209 @@ +from __future__ import annotations + +import collections.abc as cabc +import enum +import os +import stat +import sys +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 open_stream +from .exceptions import BadParameter +from .utils import format_filename +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 + +ParamTypeValue = t.TypeVar("ParamTypeValue") + + +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[str | None] = None + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + + # Custom subclasses might not remember to set a name. + if hasattr(self, "name"): + name = self.name + else: + name = param_type + + return {"param_type": param_type, "name": name} + + def __call__( + self, + value: t.Any, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> t.Any: + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param: Parameter, ctx: Context | None) -> str | None: + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> 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) -> cabc.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: Parameter | None = None, + ctx: Context | None = 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 + ) -> 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: str = func.__name__ + self.func = func + + def to_info_dict(self) -> 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: Parameter | None, ctx: Context | None + ) -> 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: Parameter | None, ctx: Context | None + ) -> t.Any: + return value + + def __repr__(self) -> str: + return "UNPROCESSED" + + +class StringParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = sys.getfilesystemencoding() + 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, t.Generic[ParamTypeValue]): + """The choice type allows a value to be checked against a fixed set + of supported values. + + You may pass any iterable value which will be converted to a tuple + and thus will only be iterated once. + + The resulting value will always be one of the originally passed choices. + See :meth:`normalize_choice` for more info on the mapping of strings + to choices. See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + + .. versionchanged:: 8.2.0 + Non-``str`` ``choices`` are now supported. It can additionally be any + iterable. Before you were not recommended to pass anything but a list or + tuple. + + .. versionadded:: 8.2.0 + Choice normalization can be overridden via :meth:`normalize_choice`. + """ + + name = "choice" + + def __init__( + self, choices: cabc.Iterable[ParamTypeValue], case_sensitive: bool = True + ) -> None: + self.choices: cabc.Sequence[ParamTypeValue] = tuple(choices) + self.case_sensitive = case_sensitive + + def to_info_dict(self) -> 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 _normalized_mapping( + self, ctx: Context | None = None + ) -> cabc.Mapping[ParamTypeValue, str]: + """ + Returns mapping where keys are the original choices and the values are + the normalized values that are accepted via the command line. + + This is a simple wrapper around :meth:`normalize_choice`, use that + instead which is supported. + """ + return { + choice: self.normalize_choice( + choice=choice, + ctx=ctx, + ) + for choice in self.choices + } + + def normalize_choice(self, choice: ParamTypeValue, ctx: Context | None) -> str: + """ + Normalize a choice value, used to map a passed string to a choice. + Each choice must have a unique normalized value. + + By default uses :meth:`Context.token_normalize_func` and if not case + sensitive, convert it to a casefolded value. + + .. versionadded:: 8.2.0 + """ + normed_value = choice.name if isinstance(choice, enum.Enum) else str(choice) + + if ctx is not None and ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(normed_value) + + if not self.case_sensitive: + normed_value = normed_value.casefold() + + return normed_value + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + if param.param_type_name == "option" and not param.show_choices: # type: ignore + choice_metavars = [ + convert_type(type(choice)).name.upper() for choice in self.choices + ] + choices_str = "|".join([*dict.fromkeys(choice_metavars)]) + else: + choices_str = "|".join( + [str(i) for i in self._normalized_mapping(ctx=ctx).values()] + ) + + # 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, ctx: Context | None) -> str: + """ + Message shown when no choice is passed. + + .. versionchanged:: 8.2.0 Added ``ctx`` argument. + """ + return _("Choose from:\n\t{choices}").format( + choices=",\n\t".join(self._normalized_mapping(ctx=ctx).values()) + ) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> ParamTypeValue: + """ + For a given value from the parser, normalize it and find its + matching normalized value in the list of choices. Then return the + matched "original" choice. + """ + normed_value = self.normalize_choice(choice=value, ctx=ctx) + normalized_mapping = self._normalized_mapping(ctx=ctx) + + try: + return next( + original + for original, normalized in normalized_mapping.items() + if normalized == normed_value + ) + except StopIteration: + self.fail( + self.get_invalid_choice_message(value=value, ctx=ctx), + param=param, + ctx=ctx, + ) + + def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str: + """Get the error message when the given choice is invalid. + + :param value: The invalid value. + + .. versionadded:: 8.2 + """ + choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values())) + return 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) + + def __repr__(self) -> str: + return f"Choice({list(self.choices)})" + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> 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: cabc.Sequence[str] | None = None): + self.formats: cabc.Sequence[str] = formats or [ + "%Y-%m-%d", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d %H:%M:%S", + ] + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["formats"] = self.formats + return info_dict + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None: + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> 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[type[t.Any]] + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> 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: float | None = None, + max: float | None = 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) -> 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: Parameter | None, ctx: Context | None + ) -> 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: t.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: t.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: float | None = None, + max: float | None = 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: t.Literal[1, -1], open: bool) -> float: + if not open: + return bound + + # Could use 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" + + bool_states: dict[str, bool] = { + "1": True, + "0": False, + "yes": True, + "no": False, + "true": True, + "false": False, + "on": True, + "off": False, + "t": True, + "f": False, + "y": True, + "n": False, + # Absence of value is considered False. + "": False, + } + """A mapping of string values to boolean states. + + Mapping is inspired by :py:attr:`configparser.ConfigParser.BOOLEAN_STATES` + and extends it. + + .. caution:: + String values are lower-cased, as the ``str_to_bool`` comparison function + below is case-insensitive. + + .. warning:: + The mapping is not exhaustive, and does not cover all possible boolean strings + representations. It will remains as it is to avoid endless bikeshedding. + + Future work my be considered to make this mapping user-configurable from public + API. + """ + + @staticmethod + def str_to_bool(value: str | bool) -> bool | None: + """Convert a string to a boolean value. + + If the value is already a boolean, it is returned as-is. If the value is a + string, it is stripped of whitespaces and lower-cased, then checked against + the known boolean states pre-defined in the `BoolParamType.bool_states` mapping + above. + + Returns `None` if the value does not match any known boolean state. + """ + if isinstance(value, bool): + return value + return BoolParamType.bool_states.get(value.strip().lower()) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> bool: + normalized = self.str_to_bool(value) + if normalized is None: + self.fail( + _( + "{value!r} is not a valid boolean. Recognized values: {states}" + ).format(value=value, states=", ".join(sorted(self.bool_states))), + param, + ctx, + ) + return normalized + + def __repr__(self) -> str: + return "BOOL" + + +class UUIDParameterType(ParamType): + name = "uuid" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> 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. + + 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. + + .. versionchanged:: 2.0 + Added the ``atomic`` parameter. + """ + + name = "filename" + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + def __init__( + self, + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = 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) -> 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: str | os.PathLike[str]) -> bool: + if self.lazy is not None: + return self.lazy + if os.fspath(value) == "-": + return False + elif "w" in self.mode: + return True + return False + + def convert( + self, + value: str | os.PathLike[str] | t.IO[t.Any], + param: Parameter | None, + ctx: Context | None, + ) -> t.IO[t.Any]: + if _is_file_like(value): + return value + + value = t.cast("str | os.PathLike[str]", value) + + try: + lazy = self.resolve_lazy_flag(value) + + if lazy: + lf = LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + if ctx is not None: + ctx.call_on_close(lf.close_intelligently) + + return t.cast("t.IO[t.Any]", lf) + + 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: + self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> 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")] + + +def _is_file_like(value: t.Any) -> te.TypeGuard[t.IO[t.Any]]: + return hasattr(value, "read") or hasattr(value, "write") + + +class Path(ParamType): + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value. + :param dir_okay: Allow a directory as a value. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + + .. versionchanged:: 8.0 + Allow passing ``path_type=pathlib.Path``. + + .. versionchanged:: 6.0 + Added the ``allow_dash`` parameter. + """ + + envvar_list_splitter: t.ClassVar[str] = 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: type[t.Any] | None = None, + executable: bool = False, + ): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.readable = readable + self.writable = writable + self.executable = executable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name: str = _("file") + elif self.dir_okay and not self.file_okay: + self.name = _("directory") + else: + self.name = _("path") + + def to_info_dict(self) -> 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, value: str | os.PathLike[str] + ) -> str | bytes | os.PathLike[str]: + if self.type is not None and not isinstance(value, self.type): + if self.type is str: + return os.fsdecode(value) + elif self.type is bytes: + return os.fsencode(value) + else: + return t.cast("os.PathLike[str]", self.type(value)) + + return value + + def convert( + self, + value: str | os.PathLike[str], + param: Parameter | None, + ctx: Context | None, + ) -> str | bytes | os.PathLike[str]: + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") + + if not is_dash: + if self.resolve_path: + rv = os.path.realpath(rv) + + 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=format_filename(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=format_filename(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=format_filename(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=format_filename(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=format_filename(value) + ), + param, + ctx, + ) + + if self.executable and not os.access(value, os.X_OK): + self.fail( + _("{name} {filename!r} is not executable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + return self.coerce_path_result(rv) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> 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: cabc.Sequence[type[t.Any] | ParamType]) -> None: + self.types: cabc.Sequence[ParamType] = [convert_type(ty) for ty in types] + + def to_info_dict(self) -> 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: Parameter | None, ctx: Context | None + ) -> 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, strict=False) + ) + + +def convert_type(ty: t.Any | None, default: t.Any | None = 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() + + +class OptionHelpExtra(t.TypedDict, total=False): + envvars: tuple[str, ...] + default: str + range: str + required: str diff --git a/web_viewer/.venv/lib/python3.12/site-packages/click/utils.py b/web_viewer/.venv/lib/python3.12/site-packages/click/utils.py new file mode 100644 index 0000000..beae26f --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/click/utils.py @@ -0,0 +1,627 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import re +import sys +import typing as t +from functools import update_wrapper +from types import ModuleType +from types import TracebackType + +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 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 + + P = te.ParamSpec("P") + +R = t.TypeVar("R") + + +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() + + +def safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]: + """Wraps a function so that it swallows exceptions.""" + + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None: + try: + return func(*args, **kwargs) + except Exception: + pass + return None + + return update_wrapper(wrapper, func) + + +def make_str(value: t.Any) -> str: + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(sys.getfilesystemencoding()) + 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 | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + atomic: bool = False, + ): + self.name: str = os.fspath(filename) + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + self._f: t.IO[t.Any] | None + self.should_close: bool + + if self.name == "-": + 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[t.Any]: + """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: + 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: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.close_intelligently() + + def __iter__(self) -> cabc.Iterator[t.AnyStr]: + self.open() + return iter(self._f) # type: ignore + + +class KeepOpenFile: + def __init__(self, file: t.IO[t.Any]) -> None: + self._file: t.IO[t.Any] = file + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._file, name) + + def __enter__(self) -> KeepOpenFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + pass + + def __repr__(self) -> str: + return repr(self._file) + + def __iter__(self) -> cabc.Iterator[t.AnyStr]: + return iter(self._file) + + +def echo( + message: t.Any | None = None, + file: t.IO[t.Any] | None = None, + nl: bool = True, + err: bool = False, + color: bool | None = 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() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + return + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, (str, bytes, bytearray)): + out: str | bytes | bytearray | None = 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, color) # type: ignore + elif not color: + out = strip_ansi(out) + + file.write(out) # type: ignore + file.flush() + + +def get_binary_stream(name: t.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: t.Literal["stdin", "stdout", "stderr"], + encoding: str | None = None, + errors: str | None = "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 | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO[t.Any]: + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. + + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python + + with open_file(filename) as f: + ... + + :param filename: The name or Path of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. + + .. versionadded:: 3.0 + """ + if lazy: + return t.cast( + "t.IO[t.Any]", 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[t.Any]", KeepOpenFile(f)) + + return f + + +def format_filename( + filename: str | bytes | os.PathLike[str] | os.PathLike[bytes], + shorten: bool = False, +) -> str: + """Format a filename as a string for display. Ensures the filename can be + displayed by replacing any invalid bytes or surrogate escapes in the name + with the replacement character ``�``. + + Invalid bytes or surrogate escapes will raise an error when written to a + stream with ``errors="strict"``. This will typically happen with ``stdout`` + when the locale is something like ``en_GB.UTF-8``. + + Many scenarios *are* safe to write surrogates though, due to PEP 538 and + PEP 540, including: + + - Writing to ``stderr``, which uses ``errors="backslashreplace"``. + - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens + stdout and stderr with ``errors="surrogateescape"``. + - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``. + - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``. + Python opens stdout and stderr with ``errors="surrogateescape"``. + + :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) + else: + filename = os.fspath(filename) + + if isinstance(filename, bytes): + filename = filename.decode(sys.getfilesystemencoding(), "replace") + else: + filename = filename.encode("utf-8", "surrogateescape").decode( + "utf-8", "replace" + ) + + return 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 effect 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[t.Any]) -> 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: str | None = None, _main: ModuleType | None = None +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if _main is None: + _main = sys.modules["__main__"] + + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + # It is set to "" inside a Shiv or PEX zipapp. + if getattr(_main, "__package__", None) in {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: cabc.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> list[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This is intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionchanged:: 8.1 + Invalid glob patterns are treated as empty expansions rather + than raising an error. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + try: + matches = glob(arg, recursive=glob_recursive) + except re.error: + matches = [] + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/INSTALLER b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/METADATA b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/METADATA new file mode 100644 index 0000000..46028fb --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/METADATA @@ -0,0 +1,91 @@ +Metadata-Version: 2.4 +Name: Flask +Version: 3.1.2 +Summary: A simple framework for building complex web applications. +Maintainer-email: Pallets +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-Expression: BSD-3-Clause +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Framework :: Flask +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Classifier: Typing :: Typed +License-File: LICENSE.txt +Requires-Dist: blinker>=1.9.0 +Requires-Dist: click>=8.1.3 +Requires-Dist: importlib-metadata>=3.6.0; python_version < '3.10' +Requires-Dist: itsdangerous>=2.2.0 +Requires-Dist: jinja2>=3.1.2 +Requires-Dist: markupsafe>=2.1.1 +Requires-Dist: werkzeug>=3.1.0 +Requires-Dist: asgiref>=3.2 ; extra == "async" +Requires-Dist: python-dotenv ; extra == "dotenv" +Project-URL: Changes, https://flask.palletsprojects.com/page/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://flask.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/flask/ +Provides-Extra: async +Provides-Extra: dotenv + +
+ +# Flask + +Flask is a lightweight [WSGI] web application framework. It is designed +to make getting started quick and easy, with the ability to scale up to +complex applications. It began as a simple wrapper around [Werkzeug] +and [Jinja], and has become one of the most popular Python web +application frameworks. + +Flask offers suggestions, but doesn't enforce any dependencies or +project layout. It is up to the developer to choose the tools and +libraries they want to use. There are many extensions provided by the +community that make adding new functionality easy. + +[WSGI]: https://wsgi.readthedocs.io/ +[Werkzeug]: https://werkzeug.palletsprojects.com/ +[Jinja]: https://jinja.palletsprojects.com/ + +## A Simple Example + +```python +# save this as app.py +from flask import Flask + +app = Flask(__name__) + +@app.route("/") +def hello(): + return "Hello, World!" +``` + +``` +$ flask run + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) +``` + +## Donate + +The Pallets organization develops and supports Flask and the libraries +it uses. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, [please +donate today]. + +[please donate today]: https://palletsprojects.com/donate + +## Contributing + +See our [detailed contributing documentation][contrib] for many ways to +contribute, including reporting issues, requesting features, asking or answering +questions, and making PRs. + +[contrib]: https://palletsprojects.com/contributing/ + diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/RECORD b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/RECORD new file mode 100644 index 0000000..3ab78d7 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/RECORD @@ -0,0 +1,58 @@ +../../../bin/flask,sha256=Da1v-xVK4BLKwY6B3jYbUQMmYNJ6B2EKmfz4mSt-MeQ,258 +flask-3.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +flask-3.1.2.dist-info/METADATA,sha256=oRg63DAAIcoLAr7kzTgIEKfm8_4HMTRpmWmIptdY_js,3167 +flask-3.1.2.dist-info/RECORD,, +flask-3.1.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask-3.1.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +flask-3.1.2.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40 +flask-3.1.2.dist-info/licenses/LICENSE.txt,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +flask/__init__.py,sha256=mHvJN9Swtl1RDtjCqCIYyIniK_SZ_l_hqUynOzgpJ9o,2701 +flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 +flask/__pycache__/__init__.cpython-312.pyc,, +flask/__pycache__/__main__.cpython-312.pyc,, +flask/__pycache__/app.cpython-312.pyc,, +flask/__pycache__/blueprints.cpython-312.pyc,, +flask/__pycache__/cli.cpython-312.pyc,, +flask/__pycache__/config.cpython-312.pyc,, +flask/__pycache__/ctx.cpython-312.pyc,, +flask/__pycache__/debughelpers.cpython-312.pyc,, +flask/__pycache__/globals.cpython-312.pyc,, +flask/__pycache__/helpers.cpython-312.pyc,, +flask/__pycache__/logging.cpython-312.pyc,, +flask/__pycache__/sessions.cpython-312.pyc,, +flask/__pycache__/signals.cpython-312.pyc,, +flask/__pycache__/templating.cpython-312.pyc,, +flask/__pycache__/testing.cpython-312.pyc,, +flask/__pycache__/typing.cpython-312.pyc,, +flask/__pycache__/views.cpython-312.pyc,, +flask/__pycache__/wrappers.cpython-312.pyc,, +flask/app.py,sha256=XGqgFRsLgBhzIoB2HSftoMTIM3hjDiH6rdV7c3g3IKc,61744 +flask/blueprints.py,sha256=p5QE2lY18GItbdr_RKRpZ8Do17g0PvQGIgZkSUDhX2k,4541 +flask/cli.py,sha256=Pfh72-BxlvoH0QHCDOc1HvXG7Kq5Xetf3zzNz2kNSHk,37184 +flask/config.py,sha256=PiqF0DPam6HW0FH4CH1hpXTBe30NSzjPEOwrz1b6kt0,13219 +flask/ctx.py,sha256=sPKzahqtgxaS7O0y9E_NzUJNUDyTD6M4GkDrVu2fU3Y,15064 +flask/debughelpers.py,sha256=PGIDhStW_efRjpaa3zHIpo-htStJOR41Ip3OJWPYBwo,6080 +flask/globals.py,sha256=XdQZmStBmPIs8t93tjx6pO7Bm3gobAaONWkFcUHaGas,1713 +flask/helpers.py,sha256=rJZge7_J288J1UQv5-kNf4oEaw332PP8NTW0QRIBbXE,23517 +flask/json/__init__.py,sha256=hLNR898paqoefdeAhraa5wyJy-bmRB2k2dV4EgVy2Z8,5602 +flask/json/__pycache__/__init__.cpython-312.pyc,, +flask/json/__pycache__/provider.cpython-312.pyc,, +flask/json/__pycache__/tag.cpython-312.pyc,, +flask/json/provider.py,sha256=5imEzY5HjV2HoUVrQbJLqXCzMNpZXfD0Y1XqdLV2XBA,7672 +flask/json/tag.py,sha256=DhaNwuIOhdt2R74oOC9Y4Z8ZprxFYiRb5dUP5byyINw,9281 +flask/logging.py,sha256=8sM3WMTubi1cBb2c_lPkWpN0J8dMAqrgKRYLLi1dCVI,2377 +flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask/sansio/README.md,sha256=-0X1tECnilmz1cogx-YhNw5d7guK7GKrq_DEV2OzlU0,228 +flask/sansio/__pycache__/app.cpython-312.pyc,, +flask/sansio/__pycache__/blueprints.cpython-312.pyc,, +flask/sansio/__pycache__/scaffold.cpython-312.pyc,, +flask/sansio/app.py,sha256=5EbxwHOchgcpZqQyalA9vyDBopknOvDg6BVwXFyFD2s,38099 +flask/sansio/blueprints.py,sha256=Tqe-7EkZ-tbWchm8iDoCfD848f0_3nLv6NNjeIPvHwM,24637 +flask/sansio/scaffold.py,sha256=wSASXYdFRWJmqcL0Xq-T7N-PDVUSiFGvjO9kPZg58bk,30371 +flask/sessions.py,sha256=duvYGmCGh_H3cgMuy2oeSjrCsCvLylF4CBKOXpN0Qms,15480 +flask/signals.py,sha256=V7lMUww7CqgJ2ThUBn1PiatZtQanOyt7OZpu2GZI-34,750 +flask/templating.py,sha256=IHsdsF-eBJPCJE0AJLCi1VhhnytOGdzHCn3yThz87c4,7536 +flask/testing.py,sha256=zzC7XxhBWOP9H697IV_4SG7Lg3Lzb5PWiyEP93_KQXE,10117 +flask/typing.py,sha256=L-L5t2jKgS0aOmVhioQ_ylqcgiVFnA6yxO-RLNhq-GU,3293 +flask/views.py,sha256=xzJx6oJqGElThtEghZN7ZQGMw5TDFyuRxUkecwRuAoA,6962 +flask/wrappers.py,sha256=jUkv4mVek2Iq4hwxd4RvqrIMb69Bv0PElDgWLmd5ORo,9406 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/REQUESTED b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/WHEEL b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/entry_points.txt b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/entry_points.txt new file mode 100644 index 0000000..eec6733 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +flask=flask.cli:main + diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/licenses/LICENSE.txt b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask-3.1.2.dist-info/licenses/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2010 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__init__.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/__init__.py new file mode 100644 index 0000000..1fdc50c --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/__init__.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import typing as t + +from . import json as json +from .app import Flask as Flask +from .blueprints import Blueprint as Blueprint +from .config import Config as Config +from .ctx import after_this_request as after_this_request +from .ctx import copy_current_request_context as copy_current_request_context +from .ctx import has_app_context as has_app_context +from .ctx import has_request_context as has_request_context +from .globals import current_app as current_app +from .globals import g as g +from .globals import request as request +from .globals import session as session +from .helpers import abort as abort +from .helpers import flash as flash +from .helpers import get_flashed_messages as get_flashed_messages +from .helpers import get_template_attribute as get_template_attribute +from .helpers import make_response as make_response +from .helpers import redirect as redirect +from .helpers import send_file as send_file +from .helpers import send_from_directory as send_from_directory +from .helpers import stream_with_context as stream_with_context +from .helpers import url_for as url_for +from .json import jsonify as jsonify +from .signals import appcontext_popped as appcontext_popped +from .signals import appcontext_pushed as appcontext_pushed +from .signals import appcontext_tearing_down as appcontext_tearing_down +from .signals import before_render_template as before_render_template +from .signals import got_request_exception as got_request_exception +from .signals import message_flashed as message_flashed +from .signals import request_finished as request_finished +from .signals import request_started as request_started +from .signals import request_tearing_down as request_tearing_down +from .signals import template_rendered as template_rendered +from .templating import render_template as render_template +from .templating import render_template_string as render_template_string +from .templating import stream_template as stream_template +from .templating import stream_template_string as stream_template_string +from .wrappers import Request as Request +from .wrappers import Response as Response + +if not t.TYPE_CHECKING: + + def __getattr__(name: str) -> t.Any: + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " Flask 3.2. Use feature detection or" + " 'importlib.metadata.version(\"flask\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("flask") + + raise AttributeError(name) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__main__.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/__main__.py new file mode 100644 index 0000000..4e28416 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0884631deb217c1cda44ca077e014056eac7fd41 GIT binary patch literal 2541 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/cli.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee6501ecf23c86486a669c054adf08d2db3356a3 GIT binary patch literal 43552 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/ctx.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/ctx.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40af02f6b11c80298b59ae694007e3450e7c0dce GIT binary patch literal 19846 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/helpers.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/helpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c4b1dbd3d971d12f76d270708958d83118f2b6f GIT binary patch literal 25574 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 JK_$kv{|VkL-d_L! literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/logging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9d2551ca8ac71de7d2c40862554e259d239a3b7 GIT binary patch literal 3275 zcmZuz-)|g89iQ3z>H8Vmu~C9aMqtOjklX=P`*2t(kocUaCXEzB;ZSwAzMZ>UdvEuc zne+K9r#+QOiBYA%L+iAv6rK<$LgJDCftR>8vbAlcptcf4JXmp%N}l-6?5)pE0g3Wmj{i~(QLpTXax5d&+(9W9gkN-}1! z8`o{y5xOuPn_q|T*ql?*tsBf)>3k(k!%~FNU$-b1Kn8x?Y*u^|a@ODpAcXhlf!IJo zuBpL>`}CsZdSMv&6s+7tBIs`{hJC6(-$WTre-Hd9n6^Pgosx#b1<{*L<}{h93rZ+s z4kKJJsy9d#NXZ2ZmRV6zWy!TF(Md0c7=dAOqF02uOfL}2sS)9ja=FcGW>)FqoEE7#cGax47?od)IBlEcE0)fAz6{E1rd`Y4 zRL))1ZNs7rluNOO-XU$>7GPazlV(I6Jo7{FB2wN+3Q!)iIkE`)RS<3{OJU}->?PZfWwi|$)xnVD9e@6TAP@n0!y3?RJy-jmhoJy-f zZNYO)H*{92f{dWHk!#b1(y~e0l;yI^pe$#Z3%O=T)E)b!?Dv0=Z1RiMA7u8>Ct;{ufKi&)@J(Rqv*vi;;DQ2_57pY2ulJ} zrB`-9A|$VkU!)#iBlsE2bRczqf(KWnyaN+$u zgsvROKJ4jbxT68@F(tvZ0n`b=UbHXoQ_3oSce)eE1Xn-2T&I;KVpe5E!0nN@p47cd zNK9MRNiT1tQrFdrE=b$7tR9awZdSwe0uYNH1PC1kzkuBBaYwFh3iZu2Gw=G})!q@w(1S zO03l5&rVws2KlAz0oSC|09Mi0|3Y7P(Z{L6pC<3N@7-Fz^@q?u(i5Ah!vDBD+TWZS z!}Enq$Q?Wqz8lc(jvMXS;O>A#F}(&T=7yV4;I;s_!(N)*NTtQ((qY$sq+E9gkNC^J z1(wfB4}T#&?4rFWN)GQN#y1n=TZwbsncdXM?(9EfgP+DzTdBfd-`IJ1X7lBl?Nnhq zUf3P}#^dzpPMUn2CcBA25QSF=4WHh~{yuv*@+6JoLwiH8s48Rim0wLv=^PHeK{NbK zGBKqxXY2A)-m8hJhGVoW_{vL~at?)>;YZFKP6O{h0e7Ko98}YBtpcj;n40k`1@1gL zu1?r@kFODa7S`in_Jwh%T_s-Ff=}NXMnVFK8f(~JNEyy?#(q?9(2`E6Z8AB1`U z(UsqR(>(y8=ew@#s~%sqWmod$UT9LYY|%%XMj28n>HwJwIyWm)yrF};GDVq~8|ewU z15o923hvyX%%dApc~VvDMj^sE34Cry#jncLje*lwJsft^2CeEXOZdQ((Eudi@8+9u zYq4PfV_t^}M&K^>)_%pEu&RYI98d=S=jI_iP zErCb(13>L5>dXl|xOW~6PyKFkXJ~SBX!6lierxE5-SjR>@1U_wG`1TY>CWy1M>c~a Wk4CR=1z&#}(6IJ2HiCzgQ~wA3Qct-6 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/sessions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c78d00bc8019f7ae80b74c505be65b01fec828e7 GIT binary patch literal 17190 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 +{% 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 + +## Contributing + +See our [detailed contributing documentation][contrib] for many ways to +contribute, including reporting issues, requesting features, asking or answering +questions, and making PRs. + +[contrib]: https://palletsprojects.com/contributing/ + diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/RECORD b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/RECORD new file mode 100644 index 0000000..0ca35a7 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/RECORD @@ -0,0 +1,57 @@ +jinja2-3.1.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +jinja2-3.1.6.dist-info/METADATA,sha256=aMVUj7Z8QTKhOJjZsx7FDGvqKr3ZFdkh8hQ1XDpkmcg,2871 +jinja2-3.1.6.dist-info/RECORD,, +jinja2-3.1.6.dist-info/WHEEL,sha256=_2ozNFCLWc93bK4WKHCO-eDUENDlo-dgc9cU3qokYO4,82 +jinja2-3.1.6.dist-info/entry_points.txt,sha256=OL85gYU1eD8cuPlikifFngXpeBjaxl6rIJ8KkC_3r-I,58 +jinja2-3.1.6.dist-info/licenses/LICENSE.txt,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +jinja2/__init__.py,sha256=xxepO9i7DHsqkQrgBEduLtfoz2QCuT6_gbL4XSN1hbU,1928 +jinja2/__pycache__/__init__.cpython-312.pyc,, +jinja2/__pycache__/_identifier.cpython-312.pyc,, +jinja2/__pycache__/async_utils.cpython-312.pyc,, +jinja2/__pycache__/bccache.cpython-312.pyc,, +jinja2/__pycache__/compiler.cpython-312.pyc,, +jinja2/__pycache__/constants.cpython-312.pyc,, +jinja2/__pycache__/debug.cpython-312.pyc,, +jinja2/__pycache__/defaults.cpython-312.pyc,, +jinja2/__pycache__/environment.cpython-312.pyc,, +jinja2/__pycache__/exceptions.cpython-312.pyc,, +jinja2/__pycache__/ext.cpython-312.pyc,, +jinja2/__pycache__/filters.cpython-312.pyc,, +jinja2/__pycache__/idtracking.cpython-312.pyc,, +jinja2/__pycache__/lexer.cpython-312.pyc,, +jinja2/__pycache__/loaders.cpython-312.pyc,, +jinja2/__pycache__/meta.cpython-312.pyc,, +jinja2/__pycache__/nativetypes.cpython-312.pyc,, +jinja2/__pycache__/nodes.cpython-312.pyc,, +jinja2/__pycache__/optimizer.cpython-312.pyc,, +jinja2/__pycache__/parser.cpython-312.pyc,, +jinja2/__pycache__/runtime.cpython-312.pyc,, +jinja2/__pycache__/sandbox.cpython-312.pyc,, +jinja2/__pycache__/tests.cpython-312.pyc,, +jinja2/__pycache__/utils.cpython-312.pyc,, +jinja2/__pycache__/visitor.cpython-312.pyc,, +jinja2/_identifier.py,sha256=_zYctNKzRqlk_murTNlzrju1FFJL7Va_Ijqqd7ii2lU,1958 +jinja2/async_utils.py,sha256=vK-PdsuorOMnWSnEkT3iUJRIkTnYgO2T6MnGxDgHI5o,2834 +jinja2/bccache.py,sha256=gh0qs9rulnXo0PhX5jTJy2UHzI8wFnQ63o_vw7nhzRg,14061 +jinja2/compiler.py,sha256=9RpCQl5X88BHllJiPsHPh295Hh0uApvwFJNQuutULeM,74131 +jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433 +jinja2/debug.py,sha256=CnHqCDHd-BVGvti_8ZsTolnXNhA3ECsY-6n_2pwU8Hw,6297 +jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267 +jinja2/environment.py,sha256=9nhrP7Ch-NbGX00wvyr4yy-uhNHq2OCc60ggGrni_fk,61513 +jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071 +jinja2/ext.py,sha256=5PF5eHfh8mXAIxXHHRB2xXbXohi8pE3nHSOxa66uS7E,31875 +jinja2/filters.py,sha256=PQ_Egd9n9jSgtnGQYyF4K5j2nYwhUIulhPnyimkdr-k,55212 +jinja2/idtracking.py,sha256=-ll5lIp73pML3ErUYiIJj7tdmWxcH_IlDv3yA_hiZYo,10555 +jinja2/lexer.py,sha256=LYiYio6br-Tep9nPcupWXsPEtjluw3p1mU-lNBVRUfk,29786 +jinja2/loaders.py,sha256=wIrnxjvcbqh5VwW28NSkfotiDq8qNCxIOSFbGUiSLB4,24055 +jinja2/meta.py,sha256=OTDPkaFvU2Hgvx-6akz7154F8BIWaRmvJcBFvwopHww,4397 +jinja2/nativetypes.py,sha256=7GIGALVJgdyL80oZJdQUaUfwSt5q2lSSZbXt0dNf_M4,4210 +jinja2/nodes.py,sha256=m1Duzcr6qhZI8JQ6VyJgUNinjAf5bQzijSmDnMsvUx8,34579 +jinja2/optimizer.py,sha256=rJnCRlQ7pZsEEmMhsQDgC_pKyDHxP5TPS6zVPGsgcu8,1651 +jinja2/parser.py,sha256=lLOFy3sEmHc5IaEHRiH1sQVnId2moUQzhyeJZTtdY30,40383 +jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jinja2/runtime.py,sha256=gDk-GvdriJXqgsGbHgrcKTP0Yp6zPXzhzrIpCFH3jAU,34249 +jinja2/sandbox.py,sha256=Mw2aitlY2I8la7FYhcX2YG9BtUYcLnD0Gh3d29cDWrY,15009 +jinja2/tests.py,sha256=VLsBhVFnWg-PxSBz1MhRnNWgP1ovXk3neO1FLQMeC9Q,5926 +jinja2/utils.py,sha256=rRp3o9e7ZKS4fyrWRbELyLcpuGVTFcnooaOa1qx_FIk,24129 +jinja2/visitor.py,sha256=EcnL1PIwf_4RVCOMxsRNuR8AXHbS1qfAdMOE2ngKJz4,3557 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/WHEEL b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/WHEEL new file mode 100644 index 0000000..23d2d7e --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.11.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/entry_points.txt b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/entry_points.txt new file mode 100644 index 0000000..abc3eae --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[babel.extractors] +jinja2=jinja2.ext:babel_extract[i18n] + diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..c37cae4 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2007 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__init__.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__init__.py new file mode 100644 index 0000000..1a423a3 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__init__.py @@ -0,0 +1,38 @@ +"""Jinja is a template engine written in pure Python. It provides a +non-XML syntax that supports inline expressions and an optional +sandboxed environment. +""" + +from .bccache import BytecodeCache as BytecodeCache +from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache +from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache +from .environment import Environment as Environment +from .environment import Template as Template +from .exceptions import TemplateAssertionError as TemplateAssertionError +from .exceptions import TemplateError as TemplateError +from .exceptions import TemplateNotFound as TemplateNotFound +from .exceptions import TemplateRuntimeError as TemplateRuntimeError +from .exceptions import TemplatesNotFound as TemplatesNotFound +from .exceptions import TemplateSyntaxError as TemplateSyntaxError +from .exceptions import UndefinedError as UndefinedError +from .loaders import BaseLoader as BaseLoader +from .loaders import ChoiceLoader as ChoiceLoader +from .loaders import DictLoader as DictLoader +from .loaders import FileSystemLoader as FileSystemLoader +from .loaders import FunctionLoader as FunctionLoader +from .loaders import ModuleLoader as ModuleLoader +from .loaders import PackageLoader as PackageLoader +from .loaders import PrefixLoader as PrefixLoader +from .runtime import ChainableUndefined as ChainableUndefined +from .runtime import DebugUndefined as DebugUndefined +from .runtime import make_logging_undefined as make_logging_undefined +from .runtime import StrictUndefined as StrictUndefined +from .runtime import Undefined as Undefined +from .utils import clear_caches as clear_caches +from .utils import is_undefined as is_undefined +from .utils import pass_context as pass_context +from .utils import pass_environment as pass_environment +from .utils import pass_eval_context as pass_eval_context +from .utils import select_autoescape as select_autoescape + +__version__ = "3.1.6" diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a774d6a8542e4b4d40a0d8ef36ea8c409da528d4 GIT binary patch literal 1665 zcmZ9M%WfMt6ox5Fwye>`l4VK0du%5yjKCTsY10Ns1Jt)(5C=xx0tGh%YKF0x%#Z_; zlBKNLUEiR6g+5E)K)?V6x@uP~f^M?vA)T?*45T05`FVJq!}+aRbr3w`zpCt~B0_(d zAbaNC!J8j)2>pyggi)9a=b~IZhv#yJ^T2tWw_E@&;DY7(Xg*%R3zipvi@0dH7%j#n zT(Z0f>|n=o3Al{QmL1>&H1-OQ5maD)^c*$}ts>co7u)GAkjF&Cfft$E# zxdFU_S1d0Bw{XjH6S$4rmREo~xMR5myoy&Xx1+Uq9j{yN0B_(8%d5bfc+>J4@D|>( zybipLw=Hi(JF$yh%bUQvc-Qh4@E+c?ybZjM_bu-L-@$h*yTEtxUEp2(!Fcar|Mf3l zGyameOu59>G)^L-sZ044l1ag-ywSt9KTQhp$~rXvmQbsNuA33!bgZ%Cq<#D<~~4YW@NT2K{`gub4C z*uCF<^!(t%NzwO15%~V3*bD5`G7ps|3r6NaCgqtKB1DO;f`}UMvGFIJ4!^pCcrjZpREhBA^Nm1Sukn9+1)ySHWbt4-fllg&W zk&>In*fO$hWXFhWWEW&o_WdE1=BoMrujma}=xyNjMNB<%O}!Hl49o>no+Ke0{tLKp zaK}UMn)du5qt{e=-65QS7qOm~*c%T=_rOz3)5GMZW|a5Rd^H|;zRx(*zTZv8Zx^4$ zw!)suLzvUtUd2KF&gXKuf9CSJ!oLp6eUzdvQgoW4hbcNt(U&QDl%gjoI!V#f6dk4L qQ|sTi`Zz@&rwAC5K|ix0^!+47_fq6#KL{c7@$a?Pk7aoeru+|SN9zXw literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27eaa33024fcd011b6c5eb6292ddcd6298e036b9 GIT binary patch literal 2146 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%0Anl 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 F{|zfSraJ%t literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36152571a1346b96f206139ca75081e2204ce241 GIT binary patch literal 104072 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24e6b98ecd11525f9d70afc729ad2cf82a64a778 GIT binary patch literal 1568 zcmX|>J&znk5QcZbAZw%xj6{k$b-;}<8A3!RK*AW=LP#X1-k#dsI(oY2`eRP}ODui@ zzlDgDl#IxdGeQ@em0SsIKMPW;1as?ZSXy` zT%7NRE^engNmHNQF8VZSb0PX#UC8sCr;KKDW;GUITY;()nzSxuI1kvCbvgB&def_q zQSnhcg2`yp=Aak9)7Hxw;i;F}tDF3yyIzM$VLxfGy)LRYz#(a^YEziz2=gf?32y3Y zUv+Yj+Sm%+oJ7$*5ew9l={%k zxlT^!Mara5)WjQPX=Xq*4^qy$SkZK;eI9OmZP`VrCo+wOX7srT%Oj<-pX70 zB}vHzm3O%`HYKq#50T!$eD)F`W#EwfSzt7QVcRA)n>4u_o ze>Vc8)nso4XVo9{obSqck>toa4ftkI{9jv}mf$wtF>E`upNH=0tXPGZRf>@UEw(%{s%idf$Zca*a- z%^??K)+4_I=jj3z9-UIT-rVYo;&_##MehO8J~*v}W2Lg6xZaVQ*{z%^L9U*b8qiWG z-kM`?3&(Noap{e+{qS`A*OTvGzWVmX&38Y({^`Zd&o5s+fBpOBo!z$Yr}3C)8U4MC z&og%van!NU;^k&vtZ&ma-plT0Rk@ckUPEOaIY{GjYLx5C>o1;-Bw0RReE8LK5skN+ yZvC^-E=D`{^?EtIfBZFfOONtR`It@Zp08Jkw;yh|+qaKC`Si0tH&4nVX8r|y6g*=9 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be475322f69471d42ab1a00907ae0c7b5508c255 GIT binary patch literal 6593 zcmai2Yit|Wm7W*L;Y*Z6$&_Wu8tajWiXOHTJF%1|actSHo5*PKx9^$)kAZeN#R{dli1(KjkO159q*DfTm<7z0d8Y zmLo67@=9nvJQaraQOOVOI%@SUB}euHDFx)l8BPj*&ok8|h2%yl{FK%n(z-#8ND;Xi zq&zbLsquTIsrNGi==rguN$6Fp@p@}$c z^QSaPo?ObvK!+!hn3Si*UR}$gq-=YUY-Ev|#46q71%id|$!qYxK^fJ$G~+g@<+m&3 z`_MK==a?L8c--1F*0d)Xyvs0|+ZBB|=5NTjyNxp0stjv{YnqG(mv-yXJ0_EEbh))$ zKO@m5lc-riLCNpdgG{SS*K&!uU6})#GGZ>@WF!XK!^zm2uFyIIdz6LR_Puo!b(?c| zIaUpsY`s04V`r%xXR;sv3hEDjT|-*q^)1^s_bEr6kUs ziJZsud^~P?(s8%0mO~$PPDoYKyK@dr7If5^L(^NGC!fishickg%JjJ!X-aK+N*lJ; z=4o>R^GvV)N@8-nF;dlC9p`E4gCOi|*5pk#QQc)Go8wo*9PFKX`^DtlUf-qS9`Oon zo_FL$lrm(&xGW3FEJCts2=bMroH0_GDj1r8WH`!6SrFAF!EjHzF3ccpJ|I9^Oc|F` zs*u#?Gbu%u1fz2Hfg{PJcBhzeSu_Mi)8+&vHAi|~B3hE-yi9ZwL#jF)XD}QfZ6+_L zbYVWL8^Sc$Dk`|QO7Eg7T)O0#ed&@gt!T-)iqL3AMB=>QC>j;cXb4)?@lTg74U(=k zl$uR}kE%!rsTtBY9_gH_Evmz$IO=$(QDG9MttwKRH*6MBhsg9AGEAjAcaDb1iDm!G zS(WH~#coa{(kV49Cft!H67e>h)t7Xe2d#;eI-}XNVRK1QH|+51qAriSi-hp8wF4>e zq`D!hNf}}9W(S}{RmWafLfB2&4cMqe)eO5~Mx2v(l=&H0KNZ%&_9`h=RyEsaOea+A zlzAX#U>c!nhB$BJ!y5fFiCIOP78TuQXJx}~gCA7GCekySDuHKDR5bZPL51{+nT)JT zHjm^58R@bV_dE8{3EPjyx*#f9*=CGs+XLfC&Oz^$ErsxLw0(}<(+I~k9eYT;I3#^_ zY2>mtFOP_e^2li|nVpB-tdAg35)s&sBaAAIEXvb~g_OJ~qmkhSSzQ=WQqv=uCF8QD zK0kcq=!g!-VJHI~i?gymLYC#|h$K&EXNNOOcFbAP9TS%zRwC?f@G;bJSX!aJ+()$? zxM5y5fB2EbwXB?6Sz7CU*w}Q#d)@mnTczETT5B-5_`wII$39q9qfA`AkU-+8V z;tx6xKkyy-G+rE?xIa2k>gvh+AMhQe1BYlcfiBUb0g{mxhJjNSDYUwg}X?X4#)(-0^y+w72*XA$Ht=7}|GY|LmZd|;>+&Nc#VcdFQ{O-cmzKQ(VQmEf zQgcheSM~r4Mi6Q(279bv&&JuU;NU%O@Ci&lg?jK02MvVdA=xoWd_dgP1O&klrwN8I z>5q@raD++MLlWto@)bR|D{&X1A(zI7VNa46iFPACL;)s+<0eF96Qb`=*qq0xMs^9} zspMIPBf1RuMDi|!CWzp^+t^A-Ij_X$d|>Cvr&w3TAWnPyPlsx>OXYZzm;7^p91$x8 zaz1sx$)_W3Z7r9A?${77g+L}O(e2canV&K_pHamv>E@b*OGyoL?}PTJ>8%@keE$@2 zHRrtQKkSIL-uiL29!3G(b+9#X>!1>AZ{XI! z7N!sQb#OOuhwExST$g+F>)hLwwaa<^lyS_hH@&9Ep=c}<70-7vJf;K~9X%%8%WHC%auN$%kL8~egC2Dql zT1JC_S0n%$Ns-_f9M%9UD2kBL;1+^$4F)`JhfZtE!4eA5vmm0BIIYOKBL|RUCq!~3 z0p*Z;%Wkw$4P#FQ2w(tcMDh?4QxF5OK}jS-uqJ)uV1|3!HI)_Q;ouo#dXMUAT*s;) z4ikAY8lg54n3Jl00?#n6Ah2tfa(7mpE~-Oy*}K|0QQc{YX~CI)0U+Vd8Llj;BRXM6 zUAzM`jUJgm(p5_qK&2~6Sc5($J4&sTH+;mh0dellG`9ci1BugFP+kE_igO*58b zhL#yp0AwiDbtah9_7Z%USax#Z5r#`~<`MR{kMQ*iUq_P~vE&iH2*wZEQH*TdiAa!X z)e*iz+5AkUXtNnDg9Ql=MhBo0;e%%f03RSj$LI@TfMAC(?g4b|pgLa#U?beW9fXcW zG@I12s$n-lVQx`Vm8B{xfDLo{w?jY^?4hTYWt)f6j3UnJ#CW{AwjWrqKXn68B$k7z z)kzPqBWeYAS?va_A%%3Kg4D3MqH2aXO@wBUEQ6qfHa?^}m8?FTkfqT*Z&EC)Wk?p2v-V1=IwX@XHQR?ddi;s(V z0ceD%eVrvfwB}uJx*o9j1NRP|u=o?7zI6B7fSvYvS5B8hRAc+fxow|+<)e}di2AH( zUokplMTZC!T3fgu>@39&7tWQ!t;Milg@ui_2jPLz{;myvvuU$=BXqB8{@fS7 zK+zYoe6jW8_kD*-k?1wEU~Zhtn_H2g0{2BERthy1LmgHKP#qB6R;U|@YZnR^NV(Gr zb>i}yg*R~qkP2rLg^3z-hqR-n1?q~SE-TbkYrCh>ldi_bK%Oi2v!3`vK9rwadvX2D zXME3J9|qeVQ#3Fm&DhW%e6%iF5uq6Aw<7)b`TjBsbzkXt{eRyZ9zRO`{^-D&F81FK z((t!~;L()I@!0i{gU>SF?oS@WI^Lj6DqVq7z^f(zP%g-#0seFW{J}DNk{hnl)yJJ$ z1Gg^65CDcj7z=&XfopC{xKt-8>U}u%ISw8YxO5e!)wD|0t2^RNk5PqRSqjmsf}aAZ z>I2XYDR^LX0U>#k^5%Rw&Zq*g+M@vgHMZV-n4IKwHAp@joO6EDZ}M|stxm6g01pA4 z83vv$xR;@BRfb0Z_HT?8PL?xP^7rDWlU?J9!ZSj&HU^ghOR7&~G_~3bkb|l8I)I-Z z-lYI^RG}h(4wj}aQmg-5g$(?%g0HX;*_|kXz}uxDLOUK}uo}NA!4)D2poc{*t4I(v zNdyxY#FT==vj8=u+6nA?!j2Pfuty|OdctOu1nP!ypcmm6ryu<kTgmF?lfZoAv*rajA<|NS& z?FboCLIanNQWCgd4m)1kLoT_*65B691iTEM9N8X1g5S=f1R-_2yU5j+{2W(^K^wxq zzlTq6cU)nB5}y0n)gN8mT-xe7v2tqL7g)Jk>UrkY*v+w@f1}uQ%<4I|)$?M3+m1BB zIS)}M##SzD$GVHLek<0$6&rX=HF%yc1oK>8#!l&K;p)2aAkw?(`PZ1$|I&7-rOa{u z*3zNATkSX7H&5MZ+B)>S6*`zdnO|D(eb^FP_y2Grk1v+3y~UPit(IpuxlMVi<%K(M zTP-i<{oBEowd_`~qtrLBIleKGzp#G#e&|rS6FMz-Q=Nmwjw4pbk*$vBA5)yC%i`Pe zTp^wRaILQtZ2aV`6+D1l*SW&EwaN7tJ_`xk`w!+%ueE&teAy4pVK|}2m2ds^%Pw%$ z;I90we*$}1$Q{@8Z~cc(4KTkO@Si&F`Q0%Z{&9LD-sVJOud$SYmkNaUtL=e;tdme& zc~+?c6y14N@nnsZqLcRzXG4T1FTORNICXCP)VIDlan|-#_A2Y9W>ub>Zshuxz7 zFYtpg()6FHz@Mq+&#Cs$sjmN~p8K2f2vpJ{i;h E0S$30Q~&?~ literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..acf21e3ee5d3d566f644313ae6745326394614a6 GIT binary patch literal 1618 zcmah}TW=dh6rT0{5<9-;;-qbwkOr%*trmnpYN=dj)0#R?nz&q+qGjuuq?_)=%&r@} zRYk=Mugz0}REfOuTX-?b%W5DJ5<;jvr3z1d;>^}d!V|menQy-HojGUD%=o8NDuLkX z|CLgYLkRuO2mTWug5xg*gm%$Agb@}rBndcxgIYijN+AIO4ryUMB1JtM)?#{GihDQ$ zI3XoG90i<|k{*r$9+5@_M1-x9AMB*3Aumo^8pUxbgA-B~C#4)7k@7et6>wTAVnG@M z-^z)PkI2L%Kr)F(uR#yG(*Ni(o-Qj*;jA=`bJ7`{2k*j=Jxk7!8ImAzk|ZOz_#z^m z$76UL3-C_BZxWBG0?y*niR8_0K;u@z0E0I%Vse%Deyrt;G>M9-L z1fI-0f%&J(c-E{m*hPc}23-^tMOEU}isoyVr!7?rJlQ&a}ckhX$~|Fjb6A!a`W>*es%Xd9p~y zTL+75dD*hK;pf_BF~c;wd~q+sPzV;cOlr%INZ%^QoLtA$yDC(mqF5bOl`We(i5}Hd zhg2xhoYWB83f&~OQy31(*EUsTtMiaRMVwKa_KXgcuA&jLt@NGzmSq|h+|#?3ya6U# zH4P_wihQ&U^I1*?=BcPx-Zv^XRdt*{WvI2>8(`Z(_XR_mQRApRI~={kB-Y z(`emd**D>2$c-2lWNxv5O<3+`bESTV@9kO}+BGiCyy3*wqR$jRuB|m{H=EueI1%Rm zrB7)K{(<<tY5cQZCPAjt<^<3&iT+HKX1XB(f<)0 z1A$(5st-+_RF!Q~T{JsAofx)Nr3zN4yg^L^s)wuFiSs@=Z* z&@|quT)A4c;P79DEA>d(Bvy4xHMW$iRZKRNo@QH>ZvS=cnr`BrMn0tPLQH;>tv{hT z3JQYoE6V+Ws)wk2h$auwg4a&}77TrN9XY%g& z)BqLS;v~10F9ZMlt6gWu*`M3_<^WxG7cPS6`Q2S5PT zm~D`I?*LtQC(eNHQ39p&?nuT>&${UX|FWf5sc11UK-r@)GjK(E4D{{g|N(pvxk literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd0374aeff5201d007209436ae072f4684ea331a GIT binary patch literal 76694 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�JGwjH1K% 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..774a0bc6b792ff1fd030aeede9e28c81efb396ec GIT binary patch literal 7732 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 Z5J*H}RfcRni9vWBy>xbeMKAo={}0|RrUL)~ literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d17bd78162b323f0e7967cdac71082aca311bc4 GIT binary patch literal 41925 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9d69905429c22df763d3ff8dffd6c19f8a6ab40 GIT binary patch literal 32088 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/loaders.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/loaders.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75b66dcb82f6042a83a0caedb10d890d6058dc44 GIT binary patch literal 32367 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 Nr@#FW!FQg?{0Ax!OJD#1 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3073894150544d607b2864c8146cbb47c2e9d71 GIT binary patch literal 7023 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 CS49~B literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/nodes.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/nodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63741939eebf0edc9f43c09798b1a9d7afd45387 GIT binary patch literal 58285 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 O*A1!g14kiq8UGhjq*inQ literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1096135a82439b89c0037fbeb53750eac4ec13f9 GIT binary patch literal 2701 zcmaJ@Uu+b|8K2qP+xzFU4L-*P(q<&wm`mv$kPtO-L;}P~pt4&Dzi?5zTJO%;d+hC8 zX7+qO=Mtd^Ddr&wmRcF4sL-fViAYr*o2RIG%}ZPwVXZ(ys`@~mE;UsVPyJ^0);7rP zNH_D%%(vg4-}im<-8cDs7Qtx$Gw%ump})&bJYcWJ#&2O+MGmSU2RoXFE1HvVbT3iY zD>_C%C%t6ds2CVgt=8ZBYTLpM;4!>(JyXd5e~Rk0!nW%k>7<>Elim5QYB;$IdL^f! zH<8nK0XcbU+(<+pR{GjnvETX=MrdZ964nUZy1PU<3AjrI2^g`NF9ORCNR@dGp^FVp zg>d1JSiVE*%yFx262P~|&&*_9pENwnrbI9}vjXAn+E!E0SOjqx==E736$cgwv^-B;3Y#@te~!3*mGQb2fDOt7(4Dnez2SNwd)`t; zS6#488NiysLDHhcwtOknVVm-LzA8ZiSh6kfvP9ls9K5vZ4UZlq9%BvC^f~i9sao3R zj|Zg1_&IPWGCaDbj$#(smhyJqTA;)t08W*13g}7eX^BXX1@oH9`iK>Pv527Hu0uo{ zd|m|bDa|VLMZh?SmiWg0^QK2dDSKZAaQZ$34i#~j^qE5i5ChXd%y7#W5+%yvC^zXZ zxSaWQup`tZW0<2`M_i_AXmA=dxo>MyY;VX5nI{|X!?cP5sY{IxzR{!K(}G?mAym`j z`>i#I>j2f#Tcm1Nf0G zr+CB>+E|8@4}RAwMI8 z?}8;vo2Ki#foU%7-VA*yK6@HCgmm^iTHp8Vvi@Cqln=pHg+J6SJ|{xsTuZKx*(Ec6 z0Dc>XphkX!x7W=@y@Ni2jOySy+|fS6e>7awN&FsvjNijS624KRga3ck#8ttkGH4s; z4sogFW0_lP2iK%^PmGSnlO4^$d*IxMn8y_e+=EZB!nT8I67(m=7h2p^&VYw!(Ejb# z?a;Hv5Qh$UR`IsbV&W`n;bLN%%LqZt40v*>&!Q0K!lYQnC8P(M&5VHRf9 zpKRJI)k)}3Ve+KxT46s_T-|K?0dGR*al*7EOsIM&>wTajD&g#@dV@hnoAqch$)A$v zs=h}hCE*A&rfGYY5T+?)jG)NLd6hr>2e4fl*-D+qY&ar2F}fr(}PK^pZvb;-(O$I&o!Uos8Wkht|lUZVHVa*+BZ}?k>`I?d}eu z!NPLx;e!+^?72O(e{E?0zYxw3uMZaP4Gb;kx+&N^5;BN4kLQjbLw`9obYeL1bpgX% z)D<#IBSA^J#(c|rds_?nr7FegVpf)qZ`G-3hFQ~ue*(~$fbKKRcbb+L-S81mp(4ZO ze;eKhQ`oImv*D6EHozfHpLo9Pb*tl7;oc zp4HPUr@IMBk01C$^LNdN{^?6!CBIB6`d+60lf4)B{wE1M-y@i!DwaZv`7mXg4zs~* zBWTON7mg!aPd9 z65(=;AA+rPOiaPlO<;__*V0(uC?Gua4>WWajonM=AHMp*t9SJST|>u5FCATddF5pY gCq8<8N-ck;U)8>>L6q^)2mQmiaB1M*2o|O8zvM-W8UO$Q literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..312606bf9fdd2a8e1266eb391c6c8a6913c31891 GIT binary patch literal 61215 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%0A2S|xJ7UtCj5vBV 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cdeebdad3b4168459e9c836918d46981dc476d5 GIT binary patch literal 18119 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 BG(`Xa literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8363446af4f3550afa0144e5db9b362e76291e2d GIT binary patch literal 9062 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 C_!^)9 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/utils.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0d56b7c81c2ecd7a5efbb29399cf176ae5cdfdf GIT binary patch literal 34875 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b9d374e0118b99cdeca00ad5770b31388acc9b8 GIT binary patch literal 5377 zcmb_gU2Gf25#A&3_$Ml~B*u;{Iawv99a=V(*lKGhO#|6d5+{Y~R!Z8cP015)C7pb_ zqwkKAsZxMb7^ngmjoKG8Xp1ndfyi--1Vw>5Pl-_!agc{l{(;!59}F~kNT2A)MS(nZ zW{;#ON-FZu1+=%^otvAP-TCHQecaj_BG7XGYE?gLCFGy@qt>MAV094&7l=Yei6ST- zO&IkkUPaWr)8eQo5ayZUI%==EPje@i^^N*Ha5@72XPrUN8O(*^ElvxbdA>7- zDes1T_`a;2vUv8$V-{pv+NM@cx8*aCdW>NvQQS~k-D5hQ)p;^mJUtmCXD)=N3y&^L zWpyP%=?J9i#uujL9Cc4KosqRHv*_%knx3SyD(D|$Tn*$roO@+0r&C-{ib6V18cHI> zrA|VQD{NfOYBo20g4vLPJfTwcZ-K58<6y*ws8V)DwQP4YWNszb^k`$8X==#OSt`yw z1pPWQ)wKJKR9zt$nzg_xRkv&=L!fa}T`0j4Ft^iAd?$k_Dr-M_sS&kaUI$HxJqY@% z&p~&A*a)?}<)OgXxZ?g2E>~BkFtMB3ugPycilB<^N0;{=EybQF?SAsg$c@OcQs9^iZuL^IZl{m}@FJqY-~!2$srp{QM78DX z`!p}+#aF@}k{9#Bgpl|APWYowC3){f;TOU%fq633T%th7X0mZ%&aHcpV?5s=BD+D> zXJgB$PC$2-+zD@633n}ry9y&W!VfP9 zcUr;=PyM|kx;Rqm=qU_b@8~H$v)s{B3isTJbT8N|k?!S4cX3ZCa%3rbJ#ysT?aPrP zrN9v$&vjA6!oj0B3Im9uuvru@!y2{$^6~?O%!^w_OKgs;K+f+HNlu7+=8hswzy#%o znw`mLYFf4H`CG%~#^YU$?@-+1Y=gROCexFurX(@wme~ykZ4u*8gWURHn-9TTb2}SD zy>8PEz_N7+Itcl$2Um9WF7N8SvFq_Kh!E^sc)lFozA*6G^RGU?I9CcEE)HA|A6|Oq zM)>euFD&!e4#h(}Nz4w=VMd{I{K=$Zq?1X`0Qf;M@5jaM*r5~Lre-z(aV92qNENIz z(48Z9JwYi@*)B=Pg`!v?_$R`(~C8uL$o;?@4_4LHCET ztFcuSsI+^fz9I<2W9jre*-Ib{!w-ZHrK=ze!)x82#6F7M!=amlXi6IX>J6zGw$-Bc`ld zP-Lf>DSr&W@fBzU9A9JBqQLLeaWyStEwQlf^5a-7>_{8{yw~YZxddSrhziGLyaOOD z0rcg+*OfZAj&p?O7y!kfmKnfu4B#IbO-@6-H(@vp^_QzKG{uD?US_cjyGmU>$W>|t zp;;-EgIcx0*U*<5pQ(PpWe7m9r}?KcCId2%wWwyeF`x##XwItOjcV_+Y8EP>#-40& zaLpD@ENUjYREi?E0&ame7^XUbw8b?>)vGLv*EQYXYN}Dd8vwQCQ^1gTR2qwZy=a(N z{?}n1)_9J2e}aww3Yhm-LVgUjVP2e<8W@0N*DHVRN@$*%AoISw1UUHts?Q z$%7vXT&z3E`BxggDsA+`63+i>OHZT?o&@rNv#mD9pSQe_7i$p#>;7Wnji|no57bx` zto;qFf?91NtUw;O@RBgDGEK3Z5KuZ`ezKm13#bN^(uG^ZSI5WB;N8&i$(am3{R-1K zsqtGbyNL%kE!7g&?F1palAQ26A)U>-L(7resQ^xK2tY*SWxZb7-DGom?|x`8RNlS!3leBM zxHx!wM|Uyyhv>~6i89?^6yFNH8Cv>Ui5`2m?>aqp+69m7@=?L}Sa*Sdra|9;mc|?n*nM`xk?+cmFbcyREZu z;-Y=4?LfIRR_J@07upw3{hPppJM~}DC@{YO literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/_identifier.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/_identifier.py new file mode 100644 index 0000000..928c150 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/_identifier.py @@ -0,0 +1,6 @@ +import re + +# generated by scripts/generate_identifier_pattern.py +pattern = re.compile( + r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950 +) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/async_utils.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/async_utils.py new file mode 100644 index 0000000..f0c1402 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/async_utils.py @@ -0,0 +1,99 @@ +import inspect +import typing as t +from functools import WRAPPER_ASSIGNMENTS +from functools import wraps + +from .utils import _PassArg +from .utils import pass_eval_context + +if t.TYPE_CHECKING: + import typing_extensions as te + +V = t.TypeVar("V") + + +def async_variant(normal_func): # type: ignore + def decorator(async_func): # type: ignore + pass_arg = _PassArg.from_obj(normal_func) + need_eval_context = pass_arg is None + + if pass_arg is _PassArg.environment: + + def is_async(args: t.Any) -> bool: + return t.cast(bool, args[0].is_async) + + else: + + def is_async(args: t.Any) -> bool: + return t.cast(bool, args[0].environment.is_async) + + # Take the doc and annotations from the sync function, but the + # name from the async function. Pallets-Sphinx-Themes + # build_function_directive expects __wrapped__ to point to the + # sync function. + async_func_attrs = ("__module__", "__name__", "__qualname__") + normal_func_attrs = tuple(set(WRAPPER_ASSIGNMENTS).difference(async_func_attrs)) + + @wraps(normal_func, assigned=normal_func_attrs) + @wraps(async_func, assigned=async_func_attrs, updated=()) + def wrapper(*args, **kwargs): # type: ignore + b = is_async(args) + + if need_eval_context: + args = args[1:] + + if b: + return async_func(*args, **kwargs) + + return normal_func(*args, **kwargs) + + if need_eval_context: + wrapper = pass_eval_context(wrapper) + + wrapper.jinja_async_variant = True # type: ignore[attr-defined] + return wrapper + + return decorator + + +_common_primitives = {int, float, bool, str, list, dict, tuple, type(None)} + + +async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V": + # Avoid a costly call to isawaitable + if type(value) in _common_primitives: + return t.cast("V", value) + + if inspect.isawaitable(value): + return await t.cast("t.Awaitable[V]", value) + + return value + + +class _IteratorToAsyncIterator(t.Generic[V]): + def __init__(self, iterator: "t.Iterator[V]"): + self._iterator = iterator + + def __aiter__(self) -> "te.Self": + return self + + async def __anext__(self) -> V: + try: + return next(self._iterator) + except StopIteration as e: + raise StopAsyncIteration(e.value) from e + + +def auto_aiter( + iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", +) -> "t.AsyncIterator[V]": + if hasattr(iterable, "__aiter__"): + return iterable.__aiter__() + else: + return _IteratorToAsyncIterator(iter(iterable)) + + +async def auto_to_list( + value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", +) -> t.List["V"]: + return [x async for x in auto_aiter(value)] diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/bccache.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/bccache.py new file mode 100644 index 0000000..ada8b09 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/bccache.py @@ -0,0 +1,408 @@ +"""The optional bytecode cache system. This is useful if you have very +complex template situations and the compilation of all those templates +slows down your application too much. + +Situations where this is useful are often forking web applications that +are initialized on the first request. +""" + +import errno +import fnmatch +import marshal +import os +import pickle +import stat +import sys +import tempfile +import typing as t +from hashlib import sha1 +from io import BytesIO +from types import CodeType + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .environment import Environment + + class _MemcachedClient(te.Protocol): + def get(self, key: str) -> bytes: ... + + def set( + self, key: str, value: bytes, timeout: t.Optional[int] = None + ) -> None: ... + + +bc_version = 5 +# Magic bytes to identify Jinja bytecode cache files. Contains the +# Python major and minor version to avoid loading incompatible bytecode +# if a project upgrades its Python version. +bc_magic = ( + b"j2" + + pickle.dumps(bc_version, 2) + + pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2) +) + + +class Bucket: + """Buckets are used to store the bytecode for one template. It's created + and initialized by the bytecode cache and passed to the loading functions. + + The buckets get an internal checksum from the cache assigned and use this + to automatically reject outdated cache material. Individual bytecode + cache subclasses don't have to care about cache invalidation. + """ + + def __init__(self, environment: "Environment", key: str, checksum: str) -> None: + self.environment = environment + self.key = key + self.checksum = checksum + self.reset() + + def reset(self) -> None: + """Resets the bucket (unloads the bytecode).""" + self.code: t.Optional[CodeType] = None + + def load_bytecode(self, f: t.BinaryIO) -> None: + """Loads bytecode from a file or file like object.""" + # make sure the magic header is correct + magic = f.read(len(bc_magic)) + if magic != bc_magic: + self.reset() + return + # the source code of the file changed, we need to reload + checksum = pickle.load(f) + if self.checksum != checksum: + self.reset() + return + # if marshal_load fails then we need to reload + try: + self.code = marshal.load(f) + except (EOFError, ValueError, TypeError): + self.reset() + return + + def write_bytecode(self, f: t.IO[bytes]) -> None: + """Dump the bytecode into the file or file like object passed.""" + if self.code is None: + raise TypeError("can't write empty bucket") + f.write(bc_magic) + pickle.dump(self.checksum, f, 2) + marshal.dump(self.code, f) + + def bytecode_from_string(self, string: bytes) -> None: + """Load bytecode from bytes.""" + self.load_bytecode(BytesIO(string)) + + def bytecode_to_string(self) -> bytes: + """Return the bytecode as bytes.""" + out = BytesIO() + self.write_bytecode(out) + return out.getvalue() + + +class BytecodeCache: + """To implement your own bytecode cache you have to subclass this class + and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of + these methods are passed a :class:`~jinja2.bccache.Bucket`. + + A very basic bytecode cache that saves the bytecode on the file system:: + + from os import path + + class MyCache(BytecodeCache): + + def __init__(self, directory): + self.directory = directory + + def load_bytecode(self, bucket): + filename = path.join(self.directory, bucket.key) + if path.exists(filename): + with open(filename, 'rb') as f: + bucket.load_bytecode(f) + + def dump_bytecode(self, bucket): + filename = path.join(self.directory, bucket.key) + with open(filename, 'wb') as f: + bucket.write_bytecode(f) + + A more advanced version of a filesystem based bytecode cache is part of + Jinja. + """ + + def load_bytecode(self, bucket: Bucket) -> None: + """Subclasses have to override this method to load bytecode into a + bucket. If they are not able to find code in the cache for the + bucket, it must not do anything. + """ + raise NotImplementedError() + + def dump_bytecode(self, bucket: Bucket) -> None: + """Subclasses have to override this method to write the bytecode + from a bucket back to the cache. If it unable to do so it must not + fail silently but raise an exception. + """ + raise NotImplementedError() + + def clear(self) -> None: + """Clears the cache. This method is not used by Jinja but should be + implemented to allow applications to clear the bytecode cache used + by a particular environment. + """ + + def get_cache_key( + self, name: str, filename: t.Optional[t.Union[str]] = None + ) -> str: + """Returns the unique hash key for this template name.""" + hash = sha1(name.encode("utf-8")) + + if filename is not None: + hash.update(f"|{filename}".encode()) + + return hash.hexdigest() + + def get_source_checksum(self, source: str) -> str: + """Returns a checksum for the source.""" + return sha1(source.encode("utf-8")).hexdigest() + + def get_bucket( + self, + environment: "Environment", + name: str, + filename: t.Optional[str], + source: str, + ) -> Bucket: + """Return a cache bucket for the given template. All arguments are + mandatory but filename may be `None`. + """ + key = self.get_cache_key(name, filename) + checksum = self.get_source_checksum(source) + bucket = Bucket(environment, key, checksum) + self.load_bytecode(bucket) + return bucket + + def set_bucket(self, bucket: Bucket) -> None: + """Put the bucket into the cache.""" + self.dump_bytecode(bucket) + + +class FileSystemBytecodeCache(BytecodeCache): + """A bytecode cache that stores bytecode on the filesystem. It accepts + two arguments: The directory where the cache items are stored and a + pattern string that is used to build the filename. + + If no directory is specified a default cache directory is selected. On + Windows the user's temp directory is used, on UNIX systems a directory + is created for the user in the system temp directory. + + The pattern can be used to have multiple separate caches operate on the + same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s`` + is replaced with the cache key. + + >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache') + + This bytecode cache supports clearing of the cache using the clear method. + """ + + def __init__( + self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache" + ) -> None: + if directory is None: + directory = self._get_default_cache_dir() + self.directory = directory + self.pattern = pattern + + def _get_default_cache_dir(self) -> str: + def _unsafe_dir() -> "te.NoReturn": + raise RuntimeError( + "Cannot determine safe temp directory. You " + "need to explicitly provide one." + ) + + tmpdir = tempfile.gettempdir() + + # On windows the temporary directory is used specific unless + # explicitly forced otherwise. We can just use that. + if os.name == "nt": + return tmpdir + if not hasattr(os, "getuid"): + _unsafe_dir() + + dirname = f"_jinja2-cache-{os.getuid()}" + actual_dir = os.path.join(tmpdir, dirname) + + try: + os.mkdir(actual_dir, stat.S_IRWXU) + except OSError as e: + if e.errno != errno.EEXIST: + raise + try: + os.chmod(actual_dir, stat.S_IRWXU) + actual_dir_stat = os.lstat(actual_dir) + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): + _unsafe_dir() + except OSError as e: + if e.errno != errno.EEXIST: + raise + + actual_dir_stat = os.lstat(actual_dir) + if ( + actual_dir_stat.st_uid != os.getuid() + or not stat.S_ISDIR(actual_dir_stat.st_mode) + or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU + ): + _unsafe_dir() + + return actual_dir + + def _get_cache_filename(self, bucket: Bucket) -> str: + return os.path.join(self.directory, self.pattern % (bucket.key,)) + + def load_bytecode(self, bucket: Bucket) -> None: + filename = self._get_cache_filename(bucket) + + # Don't test for existence before opening the file, since the + # file could disappear after the test before the open. + try: + f = open(filename, "rb") + except (FileNotFoundError, IsADirectoryError, PermissionError): + # PermissionError can occur on Windows when an operation is + # in progress, such as calling clear(). + return + + with f: + bucket.load_bytecode(f) + + def dump_bytecode(self, bucket: Bucket) -> None: + # Write to a temporary file, then rename to the real name after + # writing. This avoids another process reading the file before + # it is fully written. + name = self._get_cache_filename(bucket) + f = tempfile.NamedTemporaryFile( + mode="wb", + dir=os.path.dirname(name), + prefix=os.path.basename(name), + suffix=".tmp", + delete=False, + ) + + def remove_silent() -> None: + try: + os.remove(f.name) + except OSError: + # Another process may have called clear(). On Windows, + # another program may be holding the file open. + pass + + try: + with f: + bucket.write_bytecode(f) + except BaseException: + remove_silent() + raise + + try: + os.replace(f.name, name) + except OSError: + # Another process may have called clear(). On Windows, + # another program may be holding the file open. + remove_silent() + except BaseException: + remove_silent() + raise + + def clear(self) -> None: + # imported lazily here because google app-engine doesn't support + # write access on the file system and the function does not exist + # normally. + from os import remove + + files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",)) + for filename in files: + try: + remove(os.path.join(self.directory, filename)) + except OSError: + pass + + +class MemcachedBytecodeCache(BytecodeCache): + """This class implements a bytecode cache that uses a memcache cache for + storing the information. It does not enforce a specific memcache library + (tummy's memcache or cmemcache) but will accept any class that provides + the minimal interface required. + + Libraries compatible with this class: + + - `cachelib `_ + - `python-memcached `_ + + (Unfortunately the django cache interface is not compatible because it + does not support storing binary data, only text. You can however pass + the underlying cache client to the bytecode cache which is available + as `django.core.cache.cache._client`.) + + The minimal interface for the client passed to the constructor is this: + + .. class:: MinimalClientInterface + + .. method:: set(key, value[, timeout]) + + Stores the bytecode in the cache. `value` is a string and + `timeout` the timeout of the key. If timeout is not provided + a default timeout or no timeout should be assumed, if it's + provided it's an integer with the number of seconds the cache + item should exist. + + .. method:: get(key) + + Returns the value for the cache key. If the item does not + exist in the cache the return value must be `None`. + + The other arguments to the constructor are the prefix for all keys that + is added before the actual cache key and the timeout for the bytecode in + the cache system. We recommend a high (or no) timeout. + + This bytecode cache does not support clearing of used items in the cache. + The clear method is a no-operation function. + + .. versionadded:: 2.7 + Added support for ignoring memcache errors through the + `ignore_memcache_errors` parameter. + """ + + def __init__( + self, + client: "_MemcachedClient", + prefix: str = "jinja2/bytecode/", + timeout: t.Optional[int] = None, + ignore_memcache_errors: bool = True, + ): + self.client = client + self.prefix = prefix + self.timeout = timeout + self.ignore_memcache_errors = ignore_memcache_errors + + def load_bytecode(self, bucket: Bucket) -> None: + try: + code = self.client.get(self.prefix + bucket.key) + except Exception: + if not self.ignore_memcache_errors: + raise + else: + bucket.bytecode_from_string(code) + + def dump_bytecode(self, bucket: Bucket) -> None: + key = self.prefix + bucket.key + value = bucket.bytecode_to_string() + + try: + if self.timeout is not None: + self.client.set(key, value, self.timeout) + else: + self.client.set(key, value) + except Exception: + if not self.ignore_memcache_errors: + raise diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/compiler.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/compiler.py new file mode 100644 index 0000000..a4ff6a1 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/compiler.py @@ -0,0 +1,1998 @@ +"""Compiles nodes from the parser into Python code.""" + +import typing as t +from contextlib import contextmanager +from functools import update_wrapper +from io import StringIO +from itertools import chain +from keyword import iskeyword as is_python_keyword + +from markupsafe import escape +from markupsafe import Markup + +from . import nodes +from .exceptions import TemplateAssertionError +from .idtracking import Symbols +from .idtracking import VAR_LOAD_ALIAS +from .idtracking import VAR_LOAD_PARAMETER +from .idtracking import VAR_LOAD_RESOLVE +from .idtracking import VAR_LOAD_UNDEFINED +from .nodes import EvalContext +from .optimizer import Optimizer +from .utils import _PassArg +from .utils import concat +from .visitor import NodeVisitor + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .environment import Environment + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +operators = { + "eq": "==", + "ne": "!=", + "gt": ">", + "gteq": ">=", + "lt": "<", + "lteq": "<=", + "in": "in", + "notin": "not in", +} + + +def optimizeconst(f: F) -> F: + def new_func( + self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any + ) -> t.Any: + # Only optimize if the frame is not volatile + if self.optimizer is not None and not frame.eval_ctx.volatile: + new_node = self.optimizer.visit(node, frame.eval_ctx) + + if new_node != node: + return self.visit(new_node, frame) + + return f(self, node, frame, **kwargs) + + return update_wrapper(new_func, f) # type: ignore[return-value] + + +def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]: + @optimizeconst + def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None: + if ( + self.environment.sandboxed and op in self.environment.intercepted_binops # type: ignore + ): + self.write(f"environment.call_binop(context, {op!r}, ") + self.visit(node.left, frame) + self.write(", ") + self.visit(node.right, frame) + else: + self.write("(") + self.visit(node.left, frame) + self.write(f" {op} ") + self.visit(node.right, frame) + + self.write(")") + + return visitor + + +def _make_unop( + op: str, +) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]: + @optimizeconst + def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None: + if ( + self.environment.sandboxed and op in self.environment.intercepted_unops # type: ignore + ): + self.write(f"environment.call_unop(context, {op!r}, ") + self.visit(node.node, frame) + else: + self.write("(" + op) + self.visit(node.node, frame) + + self.write(")") + + return visitor + + +def generate( + node: nodes.Template, + environment: "Environment", + name: t.Optional[str], + filename: t.Optional[str], + stream: t.Optional[t.TextIO] = None, + defer_init: bool = False, + optimized: bool = True, +) -> t.Optional[str]: + """Generate the python source for a node tree.""" + if not isinstance(node, nodes.Template): + raise TypeError("Can't compile non template nodes") + + generator = environment.code_generator_class( + environment, name, filename, stream, defer_init, optimized + ) + generator.visit(node) + + if stream is None: + return generator.stream.getvalue() # type: ignore + + return None + + +def has_safe_repr(value: t.Any) -> bool: + """Does the node have a safe representation?""" + if value is None or value is NotImplemented or value is Ellipsis: + return True + + if type(value) in {bool, int, float, complex, range, str, Markup}: + return True + + if type(value) in {tuple, list, set, frozenset}: + return all(has_safe_repr(v) for v in value) + + if type(value) is dict: # noqa E721 + return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items()) + + return False + + +def find_undeclared( + nodes: t.Iterable[nodes.Node], names: t.Iterable[str] +) -> t.Set[str]: + """Check if the names passed are accessed undeclared. The return value + is a set of all the undeclared names from the sequence of names found. + """ + visitor = UndeclaredNameVisitor(names) + try: + for node in nodes: + visitor.visit(node) + except VisitorExit: + pass + return visitor.undeclared + + +class MacroRef: + def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None: + self.node = node + self.accesses_caller = False + self.accesses_kwargs = False + self.accesses_varargs = False + + +class Frame: + """Holds compile time information for us.""" + + def __init__( + self, + eval_ctx: EvalContext, + parent: t.Optional["Frame"] = None, + level: t.Optional[int] = None, + ) -> None: + self.eval_ctx = eval_ctx + + # the parent of this frame + self.parent = parent + + if parent is None: + self.symbols = Symbols(level=level) + + # in some dynamic inheritance situations the compiler needs to add + # write tests around output statements. + self.require_output_check = False + + # inside some tags we are using a buffer rather than yield statements. + # this for example affects {% filter %} or {% macro %}. If a frame + # is buffered this variable points to the name of the list used as + # buffer. + self.buffer: t.Optional[str] = None + + # the name of the block we're in, otherwise None. + self.block: t.Optional[str] = None + + else: + self.symbols = Symbols(parent.symbols, level=level) + self.require_output_check = parent.require_output_check + self.buffer = parent.buffer + self.block = parent.block + + # a toplevel frame is the root + soft frames such as if conditions. + self.toplevel = False + + # the root frame is basically just the outermost frame, so no if + # conditions. This information is used to optimize inheritance + # situations. + self.rootlevel = False + + # variables set inside of loops and blocks should not affect outer frames, + # but they still needs to be kept track of as part of the active context. + self.loop_frame = False + self.block_frame = False + + # track whether the frame is being used in an if-statement or conditional + # expression as it determines which errors should be raised during runtime + # or compile time. + self.soft_frame = False + + def copy(self) -> "te.Self": + """Create a copy of the current one.""" + rv = object.__new__(self.__class__) + rv.__dict__.update(self.__dict__) + rv.symbols = self.symbols.copy() + return rv + + def inner(self, isolated: bool = False) -> "Frame": + """Return an inner frame.""" + if isolated: + return Frame(self.eval_ctx, level=self.symbols.level + 1) + return Frame(self.eval_ctx, self) + + def soft(self) -> "te.Self": + """Return a soft frame. A soft frame may not be modified as + standalone thing as it shares the resources with the frame it + was created of, but it's not a rootlevel frame any longer. + + This is only used to implement if-statements and conditional + expressions. + """ + rv = self.copy() + rv.rootlevel = False + rv.soft_frame = True + return rv + + __copy__ = copy + + +class VisitorExit(RuntimeError): + """Exception used by the `UndeclaredNameVisitor` to signal a stop.""" + + +class DependencyFinderVisitor(NodeVisitor): + """A visitor that collects filter and test calls.""" + + def __init__(self) -> None: + self.filters: t.Set[str] = set() + self.tests: t.Set[str] = set() + + def visit_Filter(self, node: nodes.Filter) -> None: + self.generic_visit(node) + self.filters.add(node.name) + + def visit_Test(self, node: nodes.Test) -> None: + self.generic_visit(node) + self.tests.add(node.name) + + def visit_Block(self, node: nodes.Block) -> None: + """Stop visiting at blocks.""" + + +class UndeclaredNameVisitor(NodeVisitor): + """A visitor that checks if a name is accessed without being + declared. This is different from the frame visitor as it will + not stop at closure frames. + """ + + def __init__(self, names: t.Iterable[str]) -> None: + self.names = set(names) + self.undeclared: t.Set[str] = set() + + def visit_Name(self, node: nodes.Name) -> None: + if node.ctx == "load" and node.name in self.names: + self.undeclared.add(node.name) + if self.undeclared == self.names: + raise VisitorExit() + else: + self.names.discard(node.name) + + def visit_Block(self, node: nodes.Block) -> None: + """Stop visiting a blocks.""" + + +class CompilerExit(Exception): + """Raised if the compiler encountered a situation where it just + doesn't make sense to further process the code. Any block that + raises such an exception is not further processed. + """ + + +class CodeGenerator(NodeVisitor): + def __init__( + self, + environment: "Environment", + name: t.Optional[str], + filename: t.Optional[str], + stream: t.Optional[t.TextIO] = None, + defer_init: bool = False, + optimized: bool = True, + ) -> None: + if stream is None: + stream = StringIO() + self.environment = environment + self.name = name + self.filename = filename + self.stream = stream + self.created_block_context = False + self.defer_init = defer_init + self.optimizer: t.Optional[Optimizer] = None + + if optimized: + self.optimizer = Optimizer(environment) + + # aliases for imports + self.import_aliases: t.Dict[str, str] = {} + + # a registry for all blocks. Because blocks are moved out + # into the global python scope they are registered here + self.blocks: t.Dict[str, nodes.Block] = {} + + # the number of extends statements so far + self.extends_so_far = 0 + + # some templates have a rootlevel extends. In this case we + # can safely assume that we're a child template and do some + # more optimizations. + self.has_known_extends = False + + # the current line number + self.code_lineno = 1 + + # registry of all filters and tests (global, not block local) + self.tests: t.Dict[str, str] = {} + self.filters: t.Dict[str, str] = {} + + # the debug information + self.debug_info: t.List[t.Tuple[int, int]] = [] + self._write_debug_info: t.Optional[int] = None + + # the number of new lines before the next write() + self._new_lines = 0 + + # the line number of the last written statement + self._last_line = 0 + + # true if nothing was written so far. + self._first_write = True + + # used by the `temporary_identifier` method to get new + # unique, temporary identifier + self._last_identifier = 0 + + # the current indentation + self._indentation = 0 + + # Tracks toplevel assignments + self._assign_stack: t.List[t.Set[str]] = [] + + # Tracks parameter definition blocks + self._param_def_block: t.List[t.Set[str]] = [] + + # Tracks the current context. + self._context_reference_stack = ["context"] + + @property + def optimized(self) -> bool: + return self.optimizer is not None + + # -- Various compilation helpers + + def fail(self, msg: str, lineno: int) -> "te.NoReturn": + """Fail with a :exc:`TemplateAssertionError`.""" + raise TemplateAssertionError(msg, lineno, self.name, self.filename) + + def temporary_identifier(self) -> str: + """Get a new unique identifier.""" + self._last_identifier += 1 + return f"t_{self._last_identifier}" + + def buffer(self, frame: Frame) -> None: + """Enable buffering for the frame from that point onwards.""" + frame.buffer = self.temporary_identifier() + self.writeline(f"{frame.buffer} = []") + + def return_buffer_contents( + self, frame: Frame, force_unescaped: bool = False + ) -> None: + """Return the buffer contents of the frame.""" + if not force_unescaped: + if frame.eval_ctx.volatile: + self.writeline("if context.eval_ctx.autoescape:") + self.indent() + self.writeline(f"return Markup(concat({frame.buffer}))") + self.outdent() + self.writeline("else:") + self.indent() + self.writeline(f"return concat({frame.buffer})") + self.outdent() + return + elif frame.eval_ctx.autoescape: + self.writeline(f"return Markup(concat({frame.buffer}))") + return + self.writeline(f"return concat({frame.buffer})") + + def indent(self) -> None: + """Indent by one.""" + self._indentation += 1 + + def outdent(self, step: int = 1) -> None: + """Outdent by step.""" + self._indentation -= step + + def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None: + """Yield or write into the frame buffer.""" + if frame.buffer is None: + self.writeline("yield ", node) + else: + self.writeline(f"{frame.buffer}.append(", node) + + def end_write(self, frame: Frame) -> None: + """End the writing process started by `start_write`.""" + if frame.buffer is not None: + self.write(")") + + def simple_write( + self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None + ) -> None: + """Simple shortcut for start_write + write + end_write.""" + self.start_write(frame, node) + self.write(s) + self.end_write(frame) + + def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None: + """Visit a list of nodes as block in a frame. If the current frame + is no buffer a dummy ``if 0: yield None`` is written automatically. + """ + try: + self.writeline("pass") + for node in nodes: + self.visit(node, frame) + except CompilerExit: + pass + + def write(self, x: str) -> None: + """Write a string into the output stream.""" + if self._new_lines: + if not self._first_write: + self.stream.write("\n" * self._new_lines) + self.code_lineno += self._new_lines + if self._write_debug_info is not None: + self.debug_info.append((self._write_debug_info, self.code_lineno)) + self._write_debug_info = None + self._first_write = False + self.stream.write(" " * self._indentation) + self._new_lines = 0 + self.stream.write(x) + + def writeline( + self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0 + ) -> None: + """Combination of newline and write.""" + self.newline(node, extra) + self.write(x) + + def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None: + """Add one or more newlines before the next write.""" + self._new_lines = max(self._new_lines, 1 + extra) + if node is not None and node.lineno != self._last_line: + self._write_debug_info = node.lineno + self._last_line = node.lineno + + def signature( + self, + node: t.Union[nodes.Call, nodes.Filter, nodes.Test], + frame: Frame, + extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + ) -> None: + """Writes a function call to the stream for the current node. + A leading comma is added automatically. The extra keyword + arguments may not include python keywords otherwise a syntax + error could occur. The extra keyword arguments should be given + as python dict. + """ + # if any of the given keyword arguments is a python keyword + # we have to make sure that no invalid call is created. + kwarg_workaround = any( + is_python_keyword(t.cast(str, k)) + for k in chain((x.key for x in node.kwargs), extra_kwargs or ()) + ) + + for arg in node.args: + self.write(", ") + self.visit(arg, frame) + + if not kwarg_workaround: + for kwarg in node.kwargs: + self.write(", ") + self.visit(kwarg, frame) + if extra_kwargs is not None: + for key, value in extra_kwargs.items(): + self.write(f", {key}={value}") + if node.dyn_args: + self.write(", *") + self.visit(node.dyn_args, frame) + + if kwarg_workaround: + if node.dyn_kwargs is not None: + self.write(", **dict({") + else: + self.write(", **{") + for kwarg in node.kwargs: + self.write(f"{kwarg.key!r}: ") + self.visit(kwarg.value, frame) + self.write(", ") + if extra_kwargs is not None: + for key, value in extra_kwargs.items(): + self.write(f"{key!r}: {value}, ") + if node.dyn_kwargs is not None: + self.write("}, **") + self.visit(node.dyn_kwargs, frame) + self.write(")") + else: + self.write("}") + + elif node.dyn_kwargs is not None: + self.write(", **") + self.visit(node.dyn_kwargs, frame) + + def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None: + """Find all filter and test names used in the template and + assign them to variables in the compiled namespace. Checking + that the names are registered with the environment is done when + compiling the Filter and Test nodes. If the node is in an If or + CondExpr node, the check is done at runtime instead. + + .. versionchanged:: 3.0 + Filters and tests in If and CondExpr nodes are checked at + runtime instead of compile time. + """ + visitor = DependencyFinderVisitor() + + for node in nodes: + visitor.visit(node) + + for id_map, names, dependency in ( + (self.filters, visitor.filters, "filters"), + ( + self.tests, + visitor.tests, + "tests", + ), + ): + for name in sorted(names): + if name not in id_map: + id_map[name] = self.temporary_identifier() + + # add check during runtime that dependencies used inside of executed + # blocks are defined, as this step may be skipped during compile time + self.writeline("try:") + self.indent() + self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]") + self.outdent() + self.writeline("except KeyError:") + self.indent() + self.writeline("@internalcode") + self.writeline(f"def {id_map[name]}(*unused):") + self.indent() + self.writeline( + f'raise TemplateRuntimeError("No {dependency[:-1]}' + f' named {name!r} found.")' + ) + self.outdent() + self.outdent() + + def enter_frame(self, frame: Frame) -> None: + undefs = [] + for target, (action, param) in frame.symbols.loads.items(): + if action == VAR_LOAD_PARAMETER: + pass + elif action == VAR_LOAD_RESOLVE: + self.writeline(f"{target} = {self.get_resolve_func()}({param!r})") + elif action == VAR_LOAD_ALIAS: + self.writeline(f"{target} = {param}") + elif action == VAR_LOAD_UNDEFINED: + undefs.append(target) + else: + raise NotImplementedError("unknown load instruction") + if undefs: + self.writeline(f"{' = '.join(undefs)} = missing") + + def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None: + if not with_python_scope: + undefs = [] + for target in frame.symbols.loads: + undefs.append(target) + if undefs: + self.writeline(f"{' = '.join(undefs)} = missing") + + def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str: + return async_value if self.environment.is_async else sync_value + + def func(self, name: str) -> str: + return f"{self.choose_async()}def {name}" + + def macro_body( + self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame + ) -> t.Tuple[Frame, MacroRef]: + """Dump the function def of a macro or call block.""" + frame = frame.inner() + frame.symbols.analyze_node(node) + macro_ref = MacroRef(node) + + explicit_caller = None + skip_special_params = set() + args = [] + + for idx, arg in enumerate(node.args): + if arg.name == "caller": + explicit_caller = idx + if arg.name in ("kwargs", "varargs"): + skip_special_params.add(arg.name) + args.append(frame.symbols.ref(arg.name)) + + undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs")) + + if "caller" in undeclared: + # In older Jinja versions there was a bug that allowed caller + # to retain the special behavior even if it was mentioned in + # the argument list. However thankfully this was only really + # working if it was the last argument. So we are explicitly + # checking this now and error out if it is anywhere else in + # the argument list. + if explicit_caller is not None: + try: + node.defaults[explicit_caller - len(node.args)] + except IndexError: + self.fail( + "When defining macros or call blocks the " + 'special "caller" argument must be omitted ' + "or be given a default.", + node.lineno, + ) + else: + args.append(frame.symbols.declare_parameter("caller")) + macro_ref.accesses_caller = True + if "kwargs" in undeclared and "kwargs" not in skip_special_params: + args.append(frame.symbols.declare_parameter("kwargs")) + macro_ref.accesses_kwargs = True + if "varargs" in undeclared and "varargs" not in skip_special_params: + args.append(frame.symbols.declare_parameter("varargs")) + macro_ref.accesses_varargs = True + + # macros are delayed, they never require output checks + frame.require_output_check = False + frame.symbols.analyze_node(node) + self.writeline(f"{self.func('macro')}({', '.join(args)}):", node) + self.indent() + + self.buffer(frame) + self.enter_frame(frame) + + self.push_parameter_definitions(frame) + for idx, arg in enumerate(node.args): + ref = frame.symbols.ref(arg.name) + self.writeline(f"if {ref} is missing:") + self.indent() + try: + default = node.defaults[idx - len(node.args)] + except IndexError: + self.writeline( + f'{ref} = undefined("parameter {arg.name!r} was not provided",' + f" name={arg.name!r})" + ) + else: + self.writeline(f"{ref} = ") + self.visit(default, frame) + self.mark_parameter_stored(ref) + self.outdent() + self.pop_parameter_definitions() + + self.blockvisit(node.body, frame) + self.return_buffer_contents(frame, force_unescaped=True) + self.leave_frame(frame, with_python_scope=True) + self.outdent() + + return frame, macro_ref + + def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None: + """Dump the macro definition for the def created by macro_body.""" + arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args) + name = getattr(macro_ref.node, "name", None) + if len(macro_ref.node.args) == 1: + arg_tuple += "," + self.write( + f"Macro(environment, macro, {name!r}, ({arg_tuple})," + f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r}," + f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)" + ) + + def position(self, node: nodes.Node) -> str: + """Return a human readable position for the node.""" + rv = f"line {node.lineno}" + if self.name is not None: + rv = f"{rv} in {self.name!r}" + return rv + + def dump_local_context(self, frame: Frame) -> str: + items_kv = ", ".join( + f"{name!r}: {target}" + for name, target in frame.symbols.dump_stores().items() + ) + return f"{{{items_kv}}}" + + def write_commons(self) -> None: + """Writes a common preamble that is used by root and block functions. + Primarily this sets up common local helpers and enforces a generator + through a dead branch. + """ + self.writeline("resolve = context.resolve_or_missing") + self.writeline("undefined = environment.undefined") + self.writeline("concat = environment.concat") + # always use the standard Undefined class for the implicit else of + # conditional expressions + self.writeline("cond_expr_undefined = Undefined") + self.writeline("if 0: yield None") + + def push_parameter_definitions(self, frame: Frame) -> None: + """Pushes all parameter targets from the given frame into a local + stack that permits tracking of yet to be assigned parameters. In + particular this enables the optimization from `visit_Name` to skip + undefined expressions for parameters in macros as macros can reference + otherwise unbound parameters. + """ + self._param_def_block.append(frame.symbols.dump_param_targets()) + + def pop_parameter_definitions(self) -> None: + """Pops the current parameter definitions set.""" + self._param_def_block.pop() + + def mark_parameter_stored(self, target: str) -> None: + """Marks a parameter in the current parameter definitions as stored. + This will skip the enforced undefined checks. + """ + if self._param_def_block: + self._param_def_block[-1].discard(target) + + def push_context_reference(self, target: str) -> None: + self._context_reference_stack.append(target) + + def pop_context_reference(self) -> None: + self._context_reference_stack.pop() + + def get_context_ref(self) -> str: + return self._context_reference_stack[-1] + + def get_resolve_func(self) -> str: + target = self._context_reference_stack[-1] + if target == "context": + return "resolve" + return f"{target}.resolve" + + def derive_context(self, frame: Frame) -> str: + return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})" + + def parameter_is_undeclared(self, target: str) -> bool: + """Checks if a given target is an undeclared parameter.""" + if not self._param_def_block: + return False + return target in self._param_def_block[-1] + + def push_assign_tracking(self) -> None: + """Pushes a new layer for assignment tracking.""" + self._assign_stack.append(set()) + + def pop_assign_tracking(self, frame: Frame) -> None: + """Pops the topmost level for assignment tracking and updates the + context variables if necessary. + """ + vars = self._assign_stack.pop() + if ( + not frame.block_frame + and not frame.loop_frame + and not frame.toplevel + or not vars + ): + return + public_names = [x for x in vars if x[:1] != "_"] + if len(vars) == 1: + name = next(iter(vars)) + ref = frame.symbols.ref(name) + if frame.loop_frame: + self.writeline(f"_loop_vars[{name!r}] = {ref}") + return + if frame.block_frame: + self.writeline(f"_block_vars[{name!r}] = {ref}") + return + self.writeline(f"context.vars[{name!r}] = {ref}") + else: + if frame.loop_frame: + self.writeline("_loop_vars.update({") + elif frame.block_frame: + self.writeline("_block_vars.update({") + else: + self.writeline("context.vars.update({") + for idx, name in enumerate(sorted(vars)): + if idx: + self.write(", ") + ref = frame.symbols.ref(name) + self.write(f"{name!r}: {ref}") + self.write("})") + if not frame.block_frame and not frame.loop_frame and public_names: + if len(public_names) == 1: + self.writeline(f"context.exported_vars.add({public_names[0]!r})") + else: + names_str = ", ".join(map(repr, sorted(public_names))) + self.writeline(f"context.exported_vars.update(({names_str}))") + + # -- Statement Visitors + + def visit_Template( + self, node: nodes.Template, frame: t.Optional[Frame] = None + ) -> None: + assert frame is None, "no root frame allowed" + eval_ctx = EvalContext(self.environment, self.name) + + from .runtime import async_exported + from .runtime import exported + + if self.environment.is_async: + exported_names = sorted(exported + async_exported) + else: + exported_names = sorted(exported) + + self.writeline("from jinja2.runtime import " + ", ".join(exported_names)) + + # if we want a deferred initialization we cannot move the + # environment into a local name + envenv = "" if self.defer_init else ", environment=environment" + + # do we have an extends tag at all? If not, we can save some + # overhead by just not processing any inheritance code. + have_extends = node.find(nodes.Extends) is not None + + # find all blocks + for block in node.find_all(nodes.Block): + if block.name in self.blocks: + self.fail(f"block {block.name!r} defined twice", block.lineno) + self.blocks[block.name] = block + + # find all imports and import them + for import_ in node.find_all(nodes.ImportedName): + if import_.importname not in self.import_aliases: + imp = import_.importname + self.import_aliases[imp] = alias = self.temporary_identifier() + if "." in imp: + module, obj = imp.rsplit(".", 1) + self.writeline(f"from {module} import {obj} as {alias}") + else: + self.writeline(f"import {imp} as {alias}") + + # add the load name + self.writeline(f"name = {self.name!r}") + + # generate the root render function. + self.writeline( + f"{self.func('root')}(context, missing=missing{envenv}):", extra=1 + ) + self.indent() + self.write_commons() + + # process the root + frame = Frame(eval_ctx) + if "self" in find_undeclared(node.body, ("self",)): + ref = frame.symbols.declare_parameter("self") + self.writeline(f"{ref} = TemplateReference(context)") + frame.symbols.analyze_node(node) + frame.toplevel = frame.rootlevel = True + frame.require_output_check = have_extends and not self.has_known_extends + if have_extends: + self.writeline("parent_template = None") + self.enter_frame(frame) + self.pull_dependencies(node.body) + self.blockvisit(node.body, frame) + self.leave_frame(frame, with_python_scope=True) + self.outdent() + + # make sure that the parent root is called. + if have_extends: + if not self.has_known_extends: + self.indent() + self.writeline("if parent_template is not None:") + self.indent() + if not self.environment.is_async: + self.writeline("yield from parent_template.root_render_func(context)") + else: + self.writeline("agen = parent_template.root_render_func(context)") + self.writeline("try:") + self.indent() + self.writeline("async for event in agen:") + self.indent() + self.writeline("yield event") + self.outdent() + self.outdent() + self.writeline("finally: await agen.aclose()") + self.outdent(1 + (not self.has_known_extends)) + + # at this point we now have the blocks collected and can visit them too. + for name, block in self.blocks.items(): + self.writeline( + f"{self.func('block_' + name)}(context, missing=missing{envenv}):", + block, + 1, + ) + self.indent() + self.write_commons() + # It's important that we do not make this frame a child of the + # toplevel template. This would cause a variety of + # interesting issues with identifier tracking. + block_frame = Frame(eval_ctx) + block_frame.block_frame = True + undeclared = find_undeclared(block.body, ("self", "super")) + if "self" in undeclared: + ref = block_frame.symbols.declare_parameter("self") + self.writeline(f"{ref} = TemplateReference(context)") + if "super" in undeclared: + ref = block_frame.symbols.declare_parameter("super") + self.writeline(f"{ref} = context.super({name!r}, block_{name})") + block_frame.symbols.analyze_node(block) + block_frame.block = name + self.writeline("_block_vars = {}") + self.enter_frame(block_frame) + self.pull_dependencies(block.body) + self.blockvisit(block.body, block_frame) + self.leave_frame(block_frame, with_python_scope=True) + self.outdent() + + blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks) + self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1) + debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info) + self.writeline(f"debug_info = {debug_kv_str!r}") + + def visit_Block(self, node: nodes.Block, frame: Frame) -> None: + """Call a block and register it for the template.""" + level = 0 + if frame.toplevel: + # if we know that we are a child template, there is no need to + # check if we are one + if self.has_known_extends: + return + if self.extends_so_far > 0: + self.writeline("if parent_template is None:") + self.indent() + level += 1 + + if node.scoped: + context = self.derive_context(frame) + else: + context = self.get_context_ref() + + if node.required: + self.writeline(f"if len(context.blocks[{node.name!r}]) <= 1:", node) + self.indent() + self.writeline( + f'raise TemplateRuntimeError("Required block {node.name!r} not found")', + node, + ) + self.outdent() + + if not self.environment.is_async and frame.buffer is None: + self.writeline( + f"yield from context.blocks[{node.name!r}][0]({context})", node + ) + else: + self.writeline(f"gen = context.blocks[{node.name!r}][0]({context})") + self.writeline("try:") + self.indent() + self.writeline( + f"{self.choose_async()}for event in gen:", + node, + ) + self.indent() + self.simple_write("event", frame) + self.outdent() + self.outdent() + self.writeline( + f"finally: {self.choose_async('await gen.aclose()', 'gen.close()')}" + ) + + self.outdent(level) + + def visit_Extends(self, node: nodes.Extends, frame: Frame) -> None: + """Calls the extender.""" + if not frame.toplevel: + self.fail("cannot use extend from a non top-level scope", node.lineno) + + # if the number of extends statements in general is zero so + # far, we don't have to add a check if something extended + # the template before this one. + if self.extends_so_far > 0: + # if we have a known extends we just add a template runtime + # error into the generated code. We could catch that at compile + # time too, but i welcome it not to confuse users by throwing the + # same error at different times just "because we can". + if not self.has_known_extends: + self.writeline("if parent_template is not None:") + self.indent() + self.writeline('raise TemplateRuntimeError("extended multiple times")') + + # if we have a known extends already we don't need that code here + # as we know that the template execution will end here. + if self.has_known_extends: + raise CompilerExit() + else: + self.outdent() + + self.writeline("parent_template = environment.get_template(", node) + self.visit(node.template, frame) + self.write(f", {self.name!r})") + self.writeline("for name, parent_block in parent_template.blocks.items():") + self.indent() + self.writeline("context.blocks.setdefault(name, []).append(parent_block)") + self.outdent() + + # if this extends statement was in the root level we can take + # advantage of that information and simplify the generated code + # in the top level from this point onwards + if frame.rootlevel: + self.has_known_extends = True + + # and now we have one more + self.extends_so_far += 1 + + def visit_Include(self, node: nodes.Include, frame: Frame) -> None: + """Handles includes.""" + if node.ignore_missing: + self.writeline("try:") + self.indent() + + func_name = "get_or_select_template" + if isinstance(node.template, nodes.Const): + if isinstance(node.template.value, str): + func_name = "get_template" + elif isinstance(node.template.value, (tuple, list)): + func_name = "select_template" + elif isinstance(node.template, (nodes.Tuple, nodes.List)): + func_name = "select_template" + + self.writeline(f"template = environment.{func_name}(", node) + self.visit(node.template, frame) + self.write(f", {self.name!r})") + if node.ignore_missing: + self.outdent() + self.writeline("except TemplateNotFound:") + self.indent() + self.writeline("pass") + self.outdent() + self.writeline("else:") + self.indent() + + def loop_body() -> None: + self.indent() + self.simple_write("event", frame) + self.outdent() + + if node.with_context: + self.writeline( + f"gen = template.root_render_func(" + "template.new_context(context.get_all(), True," + f" {self.dump_local_context(frame)}))" + ) + self.writeline("try:") + self.indent() + self.writeline(f"{self.choose_async()}for event in gen:") + loop_body() + self.outdent() + self.writeline( + f"finally: {self.choose_async('await gen.aclose()', 'gen.close()')}" + ) + elif self.environment.is_async: + self.writeline( + "for event in (await template._get_default_module_async())" + "._body_stream:" + ) + loop_body() + else: + self.writeline("yield from template._get_default_module()._body_stream") + + if node.ignore_missing: + self.outdent() + + def _import_common( + self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame + ) -> None: + self.write(f"{self.choose_async('await ')}environment.get_template(") + self.visit(node.template, frame) + self.write(f", {self.name!r}).") + + if node.with_context: + f_name = f"make_module{self.choose_async('_async')}" + self.write( + f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})" + ) + else: + self.write(f"_get_default_module{self.choose_async('_async')}(context)") + + def visit_Import(self, node: nodes.Import, frame: Frame) -> None: + """Visit regular imports.""" + self.writeline(f"{frame.symbols.ref(node.target)} = ", node) + if frame.toplevel: + self.write(f"context.vars[{node.target!r}] = ") + + self._import_common(node, frame) + + if frame.toplevel and not node.target.startswith("_"): + self.writeline(f"context.exported_vars.discard({node.target!r})") + + def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None: + """Visit named imports.""" + self.newline(node) + self.write("included_template = ") + self._import_common(node, frame) + var_names = [] + discarded_names = [] + for name in node.names: + if isinstance(name, tuple): + name, alias = name + else: + alias = name + self.writeline( + f"{frame.symbols.ref(alias)} =" + f" getattr(included_template, {name!r}, missing)" + ) + self.writeline(f"if {frame.symbols.ref(alias)} is missing:") + self.indent() + # The position will contain the template name, and will be formatted + # into a string that will be compiled into an f-string. Curly braces + # in the name must be replaced with escapes so that they will not be + # executed as part of the f-string. + position = self.position(node).replace("{", "{{").replace("}", "}}") + message = ( + "the template {included_template.__name__!r}" + f" (imported on {position})" + f" does not export the requested name {name!r}" + ) + self.writeline( + f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})" + ) + self.outdent() + if frame.toplevel: + var_names.append(alias) + if not alias.startswith("_"): + discarded_names.append(alias) + + if var_names: + if len(var_names) == 1: + name = var_names[0] + self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}") + else: + names_kv = ", ".join( + f"{name!r}: {frame.symbols.ref(name)}" for name in var_names + ) + self.writeline(f"context.vars.update({{{names_kv}}})") + if discarded_names: + if len(discarded_names) == 1: + self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})") + else: + names_str = ", ".join(map(repr, discarded_names)) + self.writeline( + f"context.exported_vars.difference_update(({names_str}))" + ) + + def visit_For(self, node: nodes.For, frame: Frame) -> None: + loop_frame = frame.inner() + loop_frame.loop_frame = True + test_frame = frame.inner() + else_frame = frame.inner() + + # try to figure out if we have an extended loop. An extended loop + # is necessary if the loop is in recursive mode if the special loop + # variable is accessed in the body if the body is a scoped block. + extended_loop = ( + node.recursive + or "loop" + in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",)) + or any(block.scoped for block in node.find_all(nodes.Block)) + ) + + loop_ref = None + if extended_loop: + loop_ref = loop_frame.symbols.declare_parameter("loop") + + loop_frame.symbols.analyze_node(node, for_branch="body") + if node.else_: + else_frame.symbols.analyze_node(node, for_branch="else") + + if node.test: + loop_filter_func = self.temporary_identifier() + test_frame.symbols.analyze_node(node, for_branch="test") + self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test) + self.indent() + self.enter_frame(test_frame) + self.writeline(self.choose_async("async for ", "for ")) + self.visit(node.target, loop_frame) + self.write(" in ") + self.write(self.choose_async("auto_aiter(fiter)", "fiter")) + self.write(":") + self.indent() + self.writeline("if ", node.test) + self.visit(node.test, test_frame) + self.write(":") + self.indent() + self.writeline("yield ") + self.visit(node.target, loop_frame) + self.outdent(3) + self.leave_frame(test_frame, with_python_scope=True) + + # if we don't have an recursive loop we have to find the shadowed + # variables at that point. Because loops can be nested but the loop + # variable is a special one we have to enforce aliasing for it. + if node.recursive: + self.writeline( + f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node + ) + self.indent() + self.buffer(loop_frame) + + # Use the same buffer for the else frame + else_frame.buffer = loop_frame.buffer + + # make sure the loop variable is a special one and raise a template + # assertion error if a loop tries to write to loop + if extended_loop: + self.writeline(f"{loop_ref} = missing") + + for name in node.find_all(nodes.Name): + if name.ctx == "store" and name.name == "loop": + self.fail( + "Can't assign to special loop variable in for-loop target", + name.lineno, + ) + + if node.else_: + iteration_indicator = self.temporary_identifier() + self.writeline(f"{iteration_indicator} = 1") + + self.writeline(self.choose_async("async for ", "for "), node) + self.visit(node.target, loop_frame) + if extended_loop: + self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(") + else: + self.write(" in ") + + if node.test: + self.write(f"{loop_filter_func}(") + if node.recursive: + self.write("reciter") + else: + if self.environment.is_async and not extended_loop: + self.write("auto_aiter(") + self.visit(node.iter, frame) + if self.environment.is_async and not extended_loop: + self.write(")") + if node.test: + self.write(")") + + if node.recursive: + self.write(", undefined, loop_render_func, depth):") + else: + self.write(", undefined):" if extended_loop else ":") + + self.indent() + self.enter_frame(loop_frame) + + self.writeline("_loop_vars = {}") + self.blockvisit(node.body, loop_frame) + if node.else_: + self.writeline(f"{iteration_indicator} = 0") + self.outdent() + self.leave_frame( + loop_frame, with_python_scope=node.recursive and not node.else_ + ) + + if node.else_: + self.writeline(f"if {iteration_indicator}:") + self.indent() + self.enter_frame(else_frame) + self.blockvisit(node.else_, else_frame) + self.leave_frame(else_frame) + self.outdent() + + # if the node was recursive we have to return the buffer contents + # and start the iteration code + if node.recursive: + self.return_buffer_contents(loop_frame) + self.outdent() + self.start_write(frame, node) + self.write(f"{self.choose_async('await ')}loop(") + if self.environment.is_async: + self.write("auto_aiter(") + self.visit(node.iter, frame) + if self.environment.is_async: + self.write(")") + self.write(", loop)") + self.end_write(frame) + + # at the end of the iteration, clear any assignments made in the + # loop from the top level + if self._assign_stack: + self._assign_stack[-1].difference_update(loop_frame.symbols.stores) + + def visit_If(self, node: nodes.If, frame: Frame) -> None: + if_frame = frame.soft() + self.writeline("if ", node) + self.visit(node.test, if_frame) + self.write(":") + self.indent() + self.blockvisit(node.body, if_frame) + self.outdent() + for elif_ in node.elif_: + self.writeline("elif ", elif_) + self.visit(elif_.test, if_frame) + self.write(":") + self.indent() + self.blockvisit(elif_.body, if_frame) + self.outdent() + if node.else_: + self.writeline("else:") + self.indent() + self.blockvisit(node.else_, if_frame) + self.outdent() + + def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None: + macro_frame, macro_ref = self.macro_body(node, frame) + self.newline() + if frame.toplevel: + if not node.name.startswith("_"): + self.write(f"context.exported_vars.add({node.name!r})") + self.writeline(f"context.vars[{node.name!r}] = ") + self.write(f"{frame.symbols.ref(node.name)} = ") + self.macro_def(macro_ref, macro_frame) + + def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None: + call_frame, macro_ref = self.macro_body(node, frame) + self.writeline("caller = ") + self.macro_def(macro_ref, call_frame) + self.start_write(frame, node) + self.visit_Call(node.call, frame, forward_caller=True) + self.end_write(frame) + + def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None: + filter_frame = frame.inner() + filter_frame.symbols.analyze_node(node) + self.enter_frame(filter_frame) + self.buffer(filter_frame) + self.blockvisit(node.body, filter_frame) + self.start_write(frame, node) + self.visit_Filter(node.filter, filter_frame) + self.end_write(frame) + self.leave_frame(filter_frame) + + def visit_With(self, node: nodes.With, frame: Frame) -> None: + with_frame = frame.inner() + with_frame.symbols.analyze_node(node) + self.enter_frame(with_frame) + for target, expr in zip(node.targets, node.values): + self.newline() + self.visit(target, with_frame) + self.write(" = ") + self.visit(expr, frame) + self.blockvisit(node.body, with_frame) + self.leave_frame(with_frame) + + def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None: + self.newline(node) + self.visit(node.node, frame) + + class _FinalizeInfo(t.NamedTuple): + const: t.Optional[t.Callable[..., str]] + src: t.Optional[str] + + @staticmethod + def _default_finalize(value: t.Any) -> t.Any: + """The default finalize function if the environment isn't + configured with one. Or, if the environment has one, this is + called on that function's output for constants. + """ + return str(value) + + _finalize: t.Optional[_FinalizeInfo] = None + + def _make_finalize(self) -> _FinalizeInfo: + """Build the finalize function to be used on constants and at + runtime. Cached so it's only created once for all output nodes. + + Returns a ``namedtuple`` with the following attributes: + + ``const`` + A function to finalize constant data at compile time. + + ``src`` + Source code to output around nodes to be evaluated at + runtime. + """ + if self._finalize is not None: + return self._finalize + + finalize: t.Optional[t.Callable[..., t.Any]] + finalize = default = self._default_finalize + src = None + + if self.environment.finalize: + src = "environment.finalize(" + env_finalize = self.environment.finalize + pass_arg = { + _PassArg.context: "context", + _PassArg.eval_context: "context.eval_ctx", + _PassArg.environment: "environment", + }.get( + _PassArg.from_obj(env_finalize) # type: ignore + ) + finalize = None + + if pass_arg is None: + + def finalize(value: t.Any) -> t.Any: # noqa: F811 + return default(env_finalize(value)) + + else: + src = f"{src}{pass_arg}, " + + if pass_arg == "environment": + + def finalize(value: t.Any) -> t.Any: # noqa: F811 + return default(env_finalize(self.environment, value)) + + self._finalize = self._FinalizeInfo(finalize, src) + return self._finalize + + def _output_const_repr(self, group: t.Iterable[t.Any]) -> str: + """Given a group of constant values converted from ``Output`` + child nodes, produce a string to write to the template module + source. + """ + return repr(concat(group)) + + def _output_child_to_const( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> str: + """Try to optimize a child of an ``Output`` node by trying to + convert it to constant, finalized data at compile time. + + If :exc:`Impossible` is raised, the node is not constant and + will be evaluated at runtime. Any other exception will also be + evaluated at runtime for easier debugging. + """ + const = node.as_const(frame.eval_ctx) + + if frame.eval_ctx.autoescape: + const = escape(const) + + # Template data doesn't go through finalize. + if isinstance(node, nodes.TemplateData): + return str(const) + + return finalize.const(const) # type: ignore + + def _output_child_pre( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> None: + """Output extra source code before visiting a child of an + ``Output`` node. + """ + if frame.eval_ctx.volatile: + self.write("(escape if context.eval_ctx.autoescape else str)(") + elif frame.eval_ctx.autoescape: + self.write("escape(") + else: + self.write("str(") + + if finalize.src is not None: + self.write(finalize.src) + + def _output_child_post( + self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo + ) -> None: + """Output extra source code after visiting a child of an + ``Output`` node. + """ + self.write(")") + + if finalize.src is not None: + self.write(")") + + def visit_Output(self, node: nodes.Output, frame: Frame) -> None: + # If an extends is active, don't render outside a block. + if frame.require_output_check: + # A top-level extends is known to exist at compile time. + if self.has_known_extends: + return + + self.writeline("if parent_template is None:") + self.indent() + + finalize = self._make_finalize() + body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = [] + + # Evaluate constants at compile time if possible. Each item in + # body will be either a list of static data or a node to be + # evaluated at runtime. + for child in node.nodes: + try: + if not ( + # If the finalize function requires runtime context, + # constants can't be evaluated at compile time. + finalize.const + # Unless it's basic template data that won't be + # finalized anyway. + or isinstance(child, nodes.TemplateData) + ): + raise nodes.Impossible() + + const = self._output_child_to_const(child, frame, finalize) + except (nodes.Impossible, Exception): + # The node was not constant and needs to be evaluated at + # runtime. Or another error was raised, which is easier + # to debug at runtime. + body.append(child) + continue + + if body and isinstance(body[-1], list): + body[-1].append(const) + else: + body.append([const]) + + if frame.buffer is not None: + if len(body) == 1: + self.writeline(f"{frame.buffer}.append(") + else: + self.writeline(f"{frame.buffer}.extend((") + + self.indent() + + for item in body: + if isinstance(item, list): + # A group of constant data to join and output. + val = self._output_const_repr(item) + + if frame.buffer is None: + self.writeline("yield " + val) + else: + self.writeline(val + ",") + else: + if frame.buffer is None: + self.writeline("yield ", item) + else: + self.newline(item) + + # A node to be evaluated at runtime. + self._output_child_pre(item, frame, finalize) + self.visit(item, frame) + self._output_child_post(item, frame, finalize) + + if frame.buffer is not None: + self.write(",") + + if frame.buffer is not None: + self.outdent() + self.writeline(")" if len(body) == 1 else "))") + + if frame.require_output_check: + self.outdent() + + def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None: + self.push_assign_tracking() + + # ``a.b`` is allowed for assignment, and is parsed as an NSRef. However, + # it is only valid if it references a Namespace object. Emit a check for + # that for each ref here, before assignment code is emitted. This can't + # be done in visit_NSRef as the ref could be in the middle of a tuple. + seen_refs: t.Set[str] = set() + + for nsref in node.find_all(nodes.NSRef): + if nsref.name in seen_refs: + # Only emit the check for each reference once, in case the same + # ref is used multiple times in a tuple, `ns.a, ns.b = c, d`. + continue + + seen_refs.add(nsref.name) + ref = frame.symbols.ref(nsref.name) + self.writeline(f"if not isinstance({ref}, Namespace):") + self.indent() + self.writeline( + "raise TemplateRuntimeError" + '("cannot assign attribute on non-namespace object")' + ) + self.outdent() + + self.newline(node) + self.visit(node.target, frame) + self.write(" = ") + self.visit(node.node, frame) + self.pop_assign_tracking(frame) + + def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None: + self.push_assign_tracking() + block_frame = frame.inner() + # This is a special case. Since a set block always captures we + # will disable output checks. This way one can use set blocks + # toplevel even in extended templates. + block_frame.require_output_check = False + block_frame.symbols.analyze_node(node) + self.enter_frame(block_frame) + self.buffer(block_frame) + self.blockvisit(node.body, block_frame) + self.newline(node) + self.visit(node.target, frame) + self.write(" = (Markup if context.eval_ctx.autoescape else identity)(") + if node.filter is not None: + self.visit_Filter(node.filter, block_frame) + else: + self.write(f"concat({block_frame.buffer})") + self.write(")") + self.pop_assign_tracking(frame) + self.leave_frame(block_frame) + + # -- Expression Visitors + + def visit_Name(self, node: nodes.Name, frame: Frame) -> None: + if node.ctx == "store" and ( + frame.toplevel or frame.loop_frame or frame.block_frame + ): + if self._assign_stack: + self._assign_stack[-1].add(node.name) + ref = frame.symbols.ref(node.name) + + # If we are looking up a variable we might have to deal with the + # case where it's undefined. We can skip that case if the load + # instruction indicates a parameter which are always defined. + if node.ctx == "load": + load = frame.symbols.find_load(ref) + if not ( + load is not None + and load[0] == VAR_LOAD_PARAMETER + and not self.parameter_is_undeclared(ref) + ): + self.write( + f"(undefined(name={node.name!r}) if {ref} is missing else {ref})" + ) + return + + self.write(ref) + + def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None: + # NSRef is a dotted assignment target a.b=c, but uses a[b]=c internally. + # visit_Assign emits code to validate that each ref is to a Namespace + # object only. That can't be emitted here as the ref could be in the + # middle of a tuple assignment. + ref = frame.symbols.ref(node.name) + self.writeline(f"{ref}[{node.attr!r}]") + + def visit_Const(self, node: nodes.Const, frame: Frame) -> None: + val = node.as_const(frame.eval_ctx) + if isinstance(val, float): + self.write(str(val)) + else: + self.write(repr(val)) + + def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None: + try: + self.write(repr(node.as_const(frame.eval_ctx))) + except nodes.Impossible: + self.write( + f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})" + ) + + def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None: + self.write("(") + idx = -1 + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item, frame) + self.write(",)" if idx == 0 else ")") + + def visit_List(self, node: nodes.List, frame: Frame) -> None: + self.write("[") + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item, frame) + self.write("]") + + def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None: + self.write("{") + for idx, item in enumerate(node.items): + if idx: + self.write(", ") + self.visit(item.key, frame) + self.write(": ") + self.visit(item.value, frame) + self.write("}") + + visit_Add = _make_binop("+") + visit_Sub = _make_binop("-") + visit_Mul = _make_binop("*") + visit_Div = _make_binop("/") + visit_FloorDiv = _make_binop("//") + visit_Pow = _make_binop("**") + visit_Mod = _make_binop("%") + visit_And = _make_binop("and") + visit_Or = _make_binop("or") + visit_Pos = _make_unop("+") + visit_Neg = _make_unop("-") + visit_Not = _make_unop("not ") + + @optimizeconst + def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None: + if frame.eval_ctx.volatile: + func_name = "(markup_join if context.eval_ctx.volatile else str_join)" + elif frame.eval_ctx.autoescape: + func_name = "markup_join" + else: + func_name = "str_join" + self.write(f"{func_name}((") + for arg in node.nodes: + self.visit(arg, frame) + self.write(", ") + self.write("))") + + @optimizeconst + def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None: + self.write("(") + self.visit(node.expr, frame) + for op in node.ops: + self.visit(op, frame) + self.write(")") + + def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None: + self.write(f" {operators[node.op]} ") + self.visit(node.expr, frame) + + @optimizeconst + def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None: + if self.environment.is_async: + self.write("(await auto_await(") + + self.write("environment.getattr(") + self.visit(node.node, frame) + self.write(f", {node.attr!r})") + + if self.environment.is_async: + self.write("))") + + @optimizeconst + def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None: + # slices bypass the environment getitem method. + if isinstance(node.arg, nodes.Slice): + self.visit(node.node, frame) + self.write("[") + self.visit(node.arg, frame) + self.write("]") + else: + if self.environment.is_async: + self.write("(await auto_await(") + + self.write("environment.getitem(") + self.visit(node.node, frame) + self.write(", ") + self.visit(node.arg, frame) + self.write(")") + + if self.environment.is_async: + self.write("))") + + def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None: + if node.start is not None: + self.visit(node.start, frame) + self.write(":") + if node.stop is not None: + self.visit(node.stop, frame) + if node.step is not None: + self.write(":") + self.visit(node.step, frame) + + @contextmanager + def _filter_test_common( + self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool + ) -> t.Iterator[None]: + if self.environment.is_async: + self.write("(await auto_await(") + + if is_filter: + self.write(f"{self.filters[node.name]}(") + func = self.environment.filters.get(node.name) + else: + self.write(f"{self.tests[node.name]}(") + func = self.environment.tests.get(node.name) + + # When inside an If or CondExpr frame, allow the filter to be + # undefined at compile time and only raise an error if it's + # actually called at runtime. See pull_dependencies. + if func is None and not frame.soft_frame: + type_name = "filter" if is_filter else "test" + self.fail(f"No {type_name} named {node.name!r}.", node.lineno) + + pass_arg = { + _PassArg.context: "context", + _PassArg.eval_context: "context.eval_ctx", + _PassArg.environment: "environment", + }.get( + _PassArg.from_obj(func) # type: ignore + ) + + if pass_arg is not None: + self.write(f"{pass_arg}, ") + + # Back to the visitor function to handle visiting the target of + # the filter or test. + yield + + self.signature(node, frame) + self.write(")") + + if self.environment.is_async: + self.write("))") + + @optimizeconst + def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None: + with self._filter_test_common(node, frame, True): + # if the filter node is None we are inside a filter block + # and want to write to the current buffer + if node.node is not None: + self.visit(node.node, frame) + elif frame.eval_ctx.volatile: + self.write( + f"(Markup(concat({frame.buffer}))" + f" if context.eval_ctx.autoescape else concat({frame.buffer}))" + ) + elif frame.eval_ctx.autoescape: + self.write(f"Markup(concat({frame.buffer}))") + else: + self.write(f"concat({frame.buffer})") + + @optimizeconst + def visit_Test(self, node: nodes.Test, frame: Frame) -> None: + with self._filter_test_common(node, frame, False): + self.visit(node.node, frame) + + @optimizeconst + def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None: + frame = frame.soft() + + def write_expr2() -> None: + if node.expr2 is not None: + self.visit(node.expr2, frame) + return + + self.write( + f'cond_expr_undefined("the inline if-expression on' + f" {self.position(node)} evaluated to false and no else" + f' section was defined.")' + ) + + self.write("(") + self.visit(node.expr1, frame) + self.write(" if ") + self.visit(node.test, frame) + self.write(" else ") + write_expr2() + self.write(")") + + @optimizeconst + def visit_Call( + self, node: nodes.Call, frame: Frame, forward_caller: bool = False + ) -> None: + if self.environment.is_async: + self.write("(await auto_await(") + if self.environment.sandboxed: + self.write("environment.call(context, ") + else: + self.write("context.call(") + self.visit(node.node, frame) + extra_kwargs = {"caller": "caller"} if forward_caller else None + loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {} + block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {} + if extra_kwargs: + extra_kwargs.update(loop_kwargs, **block_kwargs) + elif loop_kwargs or block_kwargs: + extra_kwargs = dict(loop_kwargs, **block_kwargs) + self.signature(node, frame, extra_kwargs) + self.write(")") + if self.environment.is_async: + self.write("))") + + def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None: + self.write(node.key + "=") + self.visit(node.value, frame) + + # -- Unused nodes for extensions + + def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None: + self.write("Markup(") + self.visit(node.expr, frame) + self.write(")") + + def visit_MarkSafeIfAutoescape( + self, node: nodes.MarkSafeIfAutoescape, frame: Frame + ) -> None: + self.write("(Markup if context.eval_ctx.autoescape else identity)(") + self.visit(node.expr, frame) + self.write(")") + + def visit_EnvironmentAttribute( + self, node: nodes.EnvironmentAttribute, frame: Frame + ) -> None: + self.write("environment." + node.name) + + def visit_ExtensionAttribute( + self, node: nodes.ExtensionAttribute, frame: Frame + ) -> None: + self.write(f"environment.extensions[{node.identifier!r}].{node.name}") + + def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None: + self.write(self.import_aliases[node.importname]) + + def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None: + self.write(node.name) + + def visit_ContextReference( + self, node: nodes.ContextReference, frame: Frame + ) -> None: + self.write("context") + + def visit_DerivedContextReference( + self, node: nodes.DerivedContextReference, frame: Frame + ) -> None: + self.write(self.derive_context(frame)) + + def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None: + self.writeline("continue", node) + + def visit_Break(self, node: nodes.Break, frame: Frame) -> None: + self.writeline("break", node) + + def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None: + scope_frame = frame.inner() + scope_frame.symbols.analyze_node(node) + self.enter_frame(scope_frame) + self.blockvisit(node.body, scope_frame) + self.leave_frame(scope_frame) + + def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None: + ctx = self.temporary_identifier() + self.writeline(f"{ctx} = {self.derive_context(frame)}") + self.writeline(f"{ctx}.vars = ") + self.visit(node.context, frame) + self.push_context_reference(ctx) + + scope_frame = frame.inner(isolated=True) + scope_frame.symbols.analyze_node(node) + self.enter_frame(scope_frame) + self.blockvisit(node.body, scope_frame) + self.leave_frame(scope_frame) + self.pop_context_reference() + + def visit_EvalContextModifier( + self, node: nodes.EvalContextModifier, frame: Frame + ) -> None: + for keyword in node.options: + self.writeline(f"context.eval_ctx.{keyword.key} = ") + self.visit(keyword.value, frame) + try: + val = keyword.value.as_const(frame.eval_ctx) + except nodes.Impossible: + frame.eval_ctx.volatile = True + else: + setattr(frame.eval_ctx, keyword.key, val) + + def visit_ScopedEvalContextModifier( + self, node: nodes.ScopedEvalContextModifier, frame: Frame + ) -> None: + old_ctx_name = self.temporary_identifier() + saved_ctx = frame.eval_ctx.save() + self.writeline(f"{old_ctx_name} = context.eval_ctx.save()") + self.visit_EvalContextModifier(node, frame) + for child in node.body: + self.visit(child, frame) + frame.eval_ctx.revert(saved_ctx) + self.writeline(f"context.eval_ctx.revert({old_ctx_name})") diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/constants.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/constants.py new file mode 100644 index 0000000..41a1c23 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/constants.py @@ -0,0 +1,20 @@ +#: list of lorem ipsum words used by the lipsum() helper function +LOREM_IPSUM_WORDS = """\ +a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at +auctor augue bibendum blandit class commodo condimentum congue consectetuer +consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus +diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend +elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames +faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac +hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum +justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem +luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie +mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non +nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque +penatibus per pharetra phasellus placerat platea porta porttitor posuere +potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus +ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit +sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor +tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices +ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus +viverra volutpat vulputate""" diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/debug.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/debug.py new file mode 100644 index 0000000..eeeeee7 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/debug.py @@ -0,0 +1,191 @@ +import sys +import typing as t +from types import CodeType +from types import TracebackType + +from .exceptions import TemplateSyntaxError +from .utils import internal_code +from .utils import missing + +if t.TYPE_CHECKING: + from .runtime import Context + + +def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException: + """Rewrite the current exception to replace any tracebacks from + within compiled template code with tracebacks that look like they + came from the template source. + + This must be called within an ``except`` block. + + :param source: For ``TemplateSyntaxError``, the original source if + known. + :return: The original exception with the rewritten traceback. + """ + _, exc_value, tb = sys.exc_info() + exc_value = t.cast(BaseException, exc_value) + tb = t.cast(TracebackType, tb) + + if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated: + exc_value.translated = True + exc_value.source = source + # Remove the old traceback, otherwise the frames from the + # compiler still show up. + exc_value.with_traceback(None) + # Outside of runtime, so the frame isn't executing template + # code, but it still needs to point at the template. + tb = fake_traceback( + exc_value, None, exc_value.filename or "", exc_value.lineno + ) + else: + # Skip the frame for the render function. + tb = tb.tb_next + + stack = [] + + # Build the stack of traceback object, replacing any in template + # code with the source file and line information. + while tb is not None: + # Skip frames decorated with @internalcode. These are internal + # calls that aren't useful in template debugging output. + if tb.tb_frame.f_code in internal_code: + tb = tb.tb_next + continue + + template = tb.tb_frame.f_globals.get("__jinja_template__") + + if template is not None: + lineno = template.get_corresponding_lineno(tb.tb_lineno) + fake_tb = fake_traceback(exc_value, tb, template.filename, lineno) + stack.append(fake_tb) + else: + stack.append(tb) + + tb = tb.tb_next + + tb_next = None + + # Assign tb_next in reverse to avoid circular references. + for tb in reversed(stack): + tb.tb_next = tb_next + tb_next = tb + + return exc_value.with_traceback(tb_next) + + +def fake_traceback( # type: ignore + exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int +) -> TracebackType: + """Produce a new traceback object that looks like it came from the + template source instead of the compiled code. The filename, line + number, and location name will point to the template, and the local + variables will be the current template context. + + :param exc_value: The original exception to be re-raised to create + the new traceback. + :param tb: The original traceback to get the local variables and + code info from. + :param filename: The template filename. + :param lineno: The line number in the template source. + """ + if tb is not None: + # Replace the real locals with the context that would be + # available at that point in the template. + locals = get_template_locals(tb.tb_frame.f_locals) + locals.pop("__jinja_exception__", None) + else: + locals = {} + + globals = { + "__name__": filename, + "__file__": filename, + "__jinja_exception__": exc_value, + } + # Raise an exception at the correct line number. + code: CodeType = compile( + "\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec" + ) + + # Build a new code object that points to the template file and + # replaces the location with a block name. + location = "template" + + if tb is not None: + function = tb.tb_frame.f_code.co_name + + if function == "root": + location = "top-level template code" + elif function.startswith("block_"): + location = f"block {function[6:]!r}" + + if sys.version_info >= (3, 8): + code = code.replace(co_name=location) + else: + code = CodeType( + code.co_argcount, + code.co_kwonlyargcount, + code.co_nlocals, + code.co_stacksize, + code.co_flags, + code.co_code, + code.co_consts, + code.co_names, + code.co_varnames, + code.co_filename, + location, + code.co_firstlineno, + code.co_lnotab, + code.co_freevars, + code.co_cellvars, + ) + + # Execute the new code, which is guaranteed to raise, and return + # the new traceback without this frame. + try: + exec(code, globals, locals) + except BaseException: + return sys.exc_info()[2].tb_next # type: ignore + + +def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]: + """Based on the runtime locals, get the context that would be + available at that point in the template. + """ + # Start with the current template context. + ctx: t.Optional[Context] = real_locals.get("context") + + if ctx is not None: + data: t.Dict[str, t.Any] = ctx.get_all().copy() + else: + data = {} + + # Might be in a derived context that only sets local variables + # rather than pushing a context. Local variables follow the scheme + # l_depth_name. Find the highest-depth local that has a value for + # each name. + local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {} + + for name, value in real_locals.items(): + if not name.startswith("l_") or value is missing: + # Not a template variable, or no longer relevant. + continue + + try: + _, depth_str, name = name.split("_", 2) + depth = int(depth_str) + except ValueError: + continue + + cur_depth = local_overrides.get(name, (-1,))[0] + + if cur_depth < depth: + local_overrides[name] = (depth, value) + + # Modify the context with any derived context. + for name, (_, value) in local_overrides.items(): + if value is missing: + data.pop(name, None) + else: + data[name] = value + + return data diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/defaults.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/defaults.py new file mode 100644 index 0000000..638cad3 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/defaults.py @@ -0,0 +1,48 @@ +import typing as t + +from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401 +from .tests import TESTS as DEFAULT_TESTS # noqa: F401 +from .utils import Cycler +from .utils import generate_lorem_ipsum +from .utils import Joiner +from .utils import Namespace + +if t.TYPE_CHECKING: + import typing_extensions as te + +# defaults for the parser / lexer +BLOCK_START_STRING = "{%" +BLOCK_END_STRING = "%}" +VARIABLE_START_STRING = "{{" +VARIABLE_END_STRING = "}}" +COMMENT_START_STRING = "{#" +COMMENT_END_STRING = "#}" +LINE_STATEMENT_PREFIX: t.Optional[str] = None +LINE_COMMENT_PREFIX: t.Optional[str] = None +TRIM_BLOCKS = False +LSTRIP_BLOCKS = False +NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n" +KEEP_TRAILING_NEWLINE = False + +# default filters, tests and namespace + +DEFAULT_NAMESPACE = { + "range": range, + "dict": dict, + "lipsum": generate_lorem_ipsum, + "cycler": Cycler, + "joiner": Joiner, + "namespace": Namespace, +} + +# default policies +DEFAULT_POLICIES: t.Dict[str, t.Any] = { + "compiler.ascii_str": True, + "urlize.rel": "noopener", + "urlize.target": None, + "urlize.extra_schemes": None, + "truncate.leeway": 5, + "json.dumps_function": None, + "json.dumps_kwargs": {"sort_keys": True}, + "ext.i18n.trimmed": False, +} diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2/environment.py b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/environment.py new file mode 100644 index 0000000..0fc6e5b --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2/environment.py @@ -0,0 +1,1672 @@ +"""Classes for managing templates and their runtime and compile time +options. +""" + +import os +import typing +import typing as t +import weakref +from collections import ChainMap +from functools import lru_cache +from functools import partial +from functools import reduce +from types import CodeType + +from markupsafe import Markup + +from . import nodes +from .compiler import CodeGenerator +from .compiler import generate +from .defaults import BLOCK_END_STRING +from .defaults import BLOCK_START_STRING +from .defaults import COMMENT_END_STRING +from .defaults import COMMENT_START_STRING +from .defaults import DEFAULT_FILTERS # type: ignore[attr-defined] +from .defaults import DEFAULT_NAMESPACE +from .defaults import DEFAULT_POLICIES +from .defaults import DEFAULT_TESTS # type: ignore[attr-defined] +from .defaults import KEEP_TRAILING_NEWLINE +from .defaults import LINE_COMMENT_PREFIX +from .defaults import LINE_STATEMENT_PREFIX +from .defaults import LSTRIP_BLOCKS +from .defaults import NEWLINE_SEQUENCE +from .defaults import TRIM_BLOCKS +from .defaults import VARIABLE_END_STRING +from .defaults import VARIABLE_START_STRING +from .exceptions import TemplateNotFound +from .exceptions import TemplateRuntimeError +from .exceptions import TemplatesNotFound +from .exceptions import TemplateSyntaxError +from .exceptions import UndefinedError +from .lexer import get_lexer +from .lexer import Lexer +from .lexer import TokenStream +from .nodes import EvalContext +from .parser import Parser +from .runtime import Context +from .runtime import new_context +from .runtime import Undefined +from .utils import _PassArg +from .utils import concat +from .utils import consume +from .utils import import_string +from .utils import internalcode +from .utils import LRUCache +from .utils import missing + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .bccache import BytecodeCache + from .ext import Extension + from .loaders import BaseLoader + +_env_bound = t.TypeVar("_env_bound", bound="Environment") + + +# for direct template usage we have up to ten living environments +@lru_cache(maxsize=10) +def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound: + """Return a new spontaneous environment. A spontaneous environment + is used for templates created directly rather than through an + existing environment. + + :param cls: Environment class to create. + :param args: Positional arguments passed to environment. + """ + env = cls(*args) + env.shared = True + return env + + +def create_cache( + size: int, +) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]: + """Return the cache class for the given size.""" + if size == 0: + return None + + if size < 0: + return {} + + return LRUCache(size) # type: ignore + + +def copy_cache( + cache: t.Optional[t.MutableMapping[t.Any, t.Any]], +) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]: + """Create an empty copy of the given cache.""" + if cache is None: + return None + + if type(cache) is dict: # noqa E721 + return {} + + return LRUCache(cache.capacity) # type: ignore + + +def load_extensions( + environment: "Environment", + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]], +) -> t.Dict[str, "Extension"]: + """Load the extensions from the list and bind it to the environment. + Returns a dict of instantiated extensions. + """ + result = {} + + for extension in extensions: + if isinstance(extension, str): + extension = t.cast(t.Type["Extension"], import_string(extension)) + + result[extension.identifier] = extension(environment) + + return result + + +def _environment_config_check(environment: _env_bound) -> _env_bound: + """Perform a sanity check on the environment.""" + assert issubclass( + environment.undefined, Undefined + ), "'undefined' must be a subclass of 'jinja2.Undefined'." + assert ( + environment.block_start_string + != environment.variable_start_string + != environment.comment_start_string + ), "block, variable and comment start strings must be different." + assert environment.newline_sequence in { + "\r", + "\r\n", + "\n", + }, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'." + return environment + + +class Environment: + r"""The core component of Jinja is the `Environment`. It contains + important shared variables like configuration, filters, tests, + globals and others. Instances of this class may be modified if + they are not shared and if no template was loaded so far. + Modifications on environments after the first template was loaded + will lead to surprising effects and undefined behavior. + + Here are the possible initialization parameters: + + `block_start_string` + The string marking the beginning of a block. Defaults to ``'{%'``. + + `block_end_string` + The string marking the end of a block. Defaults to ``'%}'``. + + `variable_start_string` + The string marking the beginning of a print statement. + Defaults to ``'{{'``. + + `variable_end_string` + The string marking the end of a print statement. Defaults to + ``'}}'``. + + `comment_start_string` + The string marking the beginning of a comment. Defaults to ``'{#'``. + + `comment_end_string` + The string marking the end of a comment. Defaults to ``'#}'``. + + `line_statement_prefix` + If given and a string, this will be used as prefix for line based + statements. See also :ref:`line-statements`. + + `line_comment_prefix` + If given and a string, this will be used as prefix for line based + comments. See also :ref:`line-statements`. + + .. versionadded:: 2.2 + + `trim_blocks` + If this is set to ``True`` the first newline after a block is + removed (block, not variable tag!). Defaults to `False`. + + `lstrip_blocks` + If this is set to ``True`` leading spaces and tabs are stripped + from the start of a line to a block. Defaults to `False`. + + `newline_sequence` + The sequence that starts a newline. Must be one of ``'\r'``, + ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a + useful default for Linux and OS X systems as well as web + applications. + + `keep_trailing_newline` + Preserve the trailing newline when rendering templates. + The default is ``False``, which causes a single newline, + if present, to be stripped from the end of the template. + + .. versionadded:: 2.7 + + `extensions` + List of Jinja extensions to use. This can either be import paths + as strings or extension classes. For more information have a + look at :ref:`the extensions documentation `. + + `optimized` + should the optimizer be enabled? Default is ``True``. + + `undefined` + :class:`Undefined` or a subclass of it that is used to represent + undefined values in the template. + + `finalize` + A callable that can be used to process the result of a variable + expression before it is output. For example one can convert + ``None`` implicitly into an empty string here. + + `autoescape` + If set to ``True`` the XML/HTML autoescaping feature is enabled by + default. For more details about autoescaping see + :class:`~markupsafe.Markup`. As of Jinja 2.4 this can also + be a callable that is passed the template name and has to + return ``True`` or ``False`` depending on autoescape should be + enabled by default. + + .. versionchanged:: 2.4 + `autoescape` can now be a function + + `loader` + The template loader for this environment. + + `cache_size` + The size of the cache. Per default this is ``400`` which means + that if more than 400 templates are loaded the loader will clean + out the least recently used template. If the cache size is set to + ``0`` templates are recompiled all the time, if the cache size is + ``-1`` the cache will not be cleaned. + + .. versionchanged:: 2.8 + The cache size was increased to 400 from a low 50. + + `auto_reload` + Some loaders load templates from locations where the template + sources may change (ie: file system or database). If + ``auto_reload`` is set to ``True`` (default) every time a template is + requested the loader checks if the source changed and if yes, it + will reload the template. For higher performance it's possible to + disable that. + + `bytecode_cache` + If set to a bytecode cache object, this object will provide a + cache for the internal Jinja bytecode so that templates don't + have to be parsed if they were not changed. + + See :ref:`bytecode-cache` for more information. + + `enable_async` + If set to true this enables async template execution which + allows using async functions and generators. + """ + + #: if this environment is sandboxed. Modifying this variable won't make + #: the environment sandboxed though. For a real sandboxed environment + #: have a look at jinja2.sandbox. This flag alone controls the code + #: generation by the compiler. + sandboxed = False + + #: True if the environment is just an overlay + overlayed = False + + #: the environment this environment is linked to if it is an overlay + linked_to: t.Optional["Environment"] = None + + #: shared environments have this set to `True`. A shared environment + #: must not be modified + shared = False + + #: the class that is used for code generation. See + #: :class:`~jinja2.compiler.CodeGenerator` for more information. + code_generator_class: t.Type["CodeGenerator"] = CodeGenerator + + concat = "".join + + #: the context class that is used for templates. See + #: :class:`~jinja2.runtime.Context` for more information. + context_class: t.Type[Context] = Context + + template_class: t.Type["Template"] + + def __init__( + self, + block_start_string: str = BLOCK_START_STRING, + block_end_string: str = BLOCK_END_STRING, + variable_start_string: str = VARIABLE_START_STRING, + variable_end_string: str = VARIABLE_END_STRING, + comment_start_string: str = COMMENT_START_STRING, + comment_end_string: str = COMMENT_END_STRING, + line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX, + line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX, + trim_blocks: bool = TRIM_BLOCKS, + lstrip_blocks: bool = LSTRIP_BLOCKS, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE, + keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (), + optimized: bool = True, + undefined: t.Type[Undefined] = Undefined, + finalize: t.Optional[t.Callable[..., t.Any]] = None, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False, + loader: t.Optional["BaseLoader"] = None, + cache_size: int = 400, + auto_reload: bool = True, + bytecode_cache: t.Optional["BytecodeCache"] = None, + enable_async: bool = False, + ): + # !!Important notice!! + # The constructor accepts quite a few arguments that should be + # passed by keyword rather than position. However it's important to + # not change the order of arguments because it's used at least + # internally in those cases: + # - spontaneous environments (i18n extension and Template) + # - unittests + # If parameter changes are required only add parameters at the end + # and don't change the arguments (or the defaults!) of the arguments + # existing already. + + # lexer / parser information + self.block_start_string = block_start_string + self.block_end_string = block_end_string + self.variable_start_string = variable_start_string + self.variable_end_string = variable_end_string + self.comment_start_string = comment_start_string + self.comment_end_string = comment_end_string + self.line_statement_prefix = line_statement_prefix + self.line_comment_prefix = line_comment_prefix + self.trim_blocks = trim_blocks + self.lstrip_blocks = lstrip_blocks + self.newline_sequence = newline_sequence + self.keep_trailing_newline = keep_trailing_newline + + # runtime information + self.undefined: t.Type[Undefined] = undefined + self.optimized = optimized + self.finalize = finalize + self.autoescape = autoescape + + # defaults + self.filters = DEFAULT_FILTERS.copy() + self.tests = DEFAULT_TESTS.copy() + self.globals = DEFAULT_NAMESPACE.copy() + + # set the loader provided + self.loader = loader + self.cache = create_cache(cache_size) + self.bytecode_cache = bytecode_cache + self.auto_reload = auto_reload + + # configurable policies + self.policies = DEFAULT_POLICIES.copy() + + # load extensions + self.extensions = load_extensions(self, extensions) + + self.is_async = enable_async + _environment_config_check(self) + + def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None: + """Adds an extension after the environment was created. + + .. versionadded:: 2.5 + """ + self.extensions.update(load_extensions(self, [extension])) + + def extend(self, **attributes: t.Any) -> None: + """Add the items to the instance of the environment if they do not exist + yet. This is used by :ref:`extensions ` to register + callbacks and configuration values without breaking inheritance. + """ + for key, value in attributes.items(): + if not hasattr(self, key): + setattr(self, key, value) + + def overlay( + self, + block_start_string: str = missing, + block_end_string: str = missing, + variable_start_string: str = missing, + variable_end_string: str = missing, + comment_start_string: str = missing, + comment_end_string: str = missing, + line_statement_prefix: t.Optional[str] = missing, + line_comment_prefix: t.Optional[str] = missing, + trim_blocks: bool = missing, + lstrip_blocks: bool = missing, + newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing, + keep_trailing_newline: bool = missing, + extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing, + optimized: bool = missing, + undefined: t.Type[Undefined] = missing, + finalize: t.Optional[t.Callable[..., t.Any]] = missing, + autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing, + loader: t.Optional["BaseLoader"] = missing, + cache_size: int = missing, + auto_reload: bool = missing, + bytecode_cache: t.Optional["BytecodeCache"] = missing, + enable_async: bool = missing, + ) -> "te.Self": + """Create a new overlay environment that shares all the data with the + current environment except for cache and the overridden attributes. + Extensions cannot be removed for an overlayed environment. An overlayed + environment automatically gets all the extensions of the environment it + is linked to plus optional extra extensions. + + Creating overlays should happen after the initial environment was set + up completely. Not all attributes are truly linked, some are just + copied over so modifications on the original environment may not shine + through. + + .. versionchanged:: 3.1.5 + ``enable_async`` is applied correctly. + + .. versionchanged:: 3.1.2 + Added the ``newline_sequence``, ``keep_trailing_newline``, + and ``enable_async`` parameters to match ``__init__``. + """ + args = dict(locals()) + del args["self"], args["cache_size"], args["extensions"], args["enable_async"] + + rv = object.__new__(self.__class__) + rv.__dict__.update(self.__dict__) + rv.overlayed = True + rv.linked_to = self + + for key, value in args.items(): + if value is not missing: + setattr(rv, key, value) + + if cache_size is not missing: + rv.cache = create_cache(cache_size) + else: + rv.cache = copy_cache(self.cache) + + rv.extensions = {} + for key, value in self.extensions.items(): + rv.extensions[key] = value.bind(rv) + if extensions is not missing: + rv.extensions.update(load_extensions(rv, extensions)) + + if enable_async is not missing: + rv.is_async = enable_async + + return _environment_config_check(rv) + + @property + def lexer(self) -> Lexer: + """The lexer for this environment.""" + return get_lexer(self) + + def iter_extensions(self) -> t.Iterator["Extension"]: + """Iterates over the extensions by priority.""" + return iter(sorted(self.extensions.values(), key=lambda x: x.priority)) + + def getitem( + self, obj: t.Any, argument: t.Union[str, t.Any] + ) -> t.Union[t.Any, Undefined]: + """Get an item or attribute of an object but prefer the item.""" + try: + return obj[argument] + except (AttributeError, TypeError, LookupError): + if isinstance(argument, str): + try: + attr = str(argument) + except Exception: + pass + else: + try: + return getattr(obj, attr) + except AttributeError: + pass + return self.undefined(obj=obj, name=argument) + + def getattr(self, obj: t.Any, attribute: str) -> t.Any: + """Get an item or attribute of an object but prefer the attribute. + Unlike :meth:`getitem` the attribute *must* be a string. + """ + try: + return getattr(obj, attribute) + except AttributeError: + pass + try: + return obj[attribute] + except (TypeError, LookupError, AttributeError): + return self.undefined(obj=obj, name=attribute) + + def _filter_test_common( + self, + name: t.Union[str, Undefined], + value: t.Any, + args: t.Optional[t.Sequence[t.Any]], + kwargs: t.Optional[t.Mapping[str, t.Any]], + context: t.Optional[Context], + eval_ctx: t.Optional[EvalContext], + is_filter: bool, + ) -> t.Any: + if is_filter: + env_map = self.filters + type_name = "filter" + else: + env_map = self.tests + type_name = "test" + + func = env_map.get(name) # type: ignore + + if func is None: + msg = f"No {type_name} named {name!r}." + + if isinstance(name, Undefined): + try: + name._fail_with_undefined_error() + except Exception as e: + msg = f"{msg} ({e}; did you forget to quote the callable name?)" + + raise TemplateRuntimeError(msg) + + args = [value, *(args if args is not None else ())] + kwargs = kwargs if kwargs is not None else {} + pass_arg = _PassArg.from_obj(func) + + if pass_arg is _PassArg.context: + if context is None: + raise TemplateRuntimeError( + f"Attempted to invoke a context {type_name} without context." + ) + + args.insert(0, context) + elif pass_arg is _PassArg.eval_context: + if eval_ctx is None: + if context is not None: + eval_ctx = context.eval_ctx + else: + eval_ctx = EvalContext(self) + + args.insert(0, eval_ctx) + elif pass_arg is _PassArg.environment: + args.insert(0, self) + + return func(*args, **kwargs) + + def call_filter( + self, + name: str, + value: t.Any, + args: t.Optional[t.Sequence[t.Any]] = None, + kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + context: t.Optional[Context] = None, + eval_ctx: t.Optional[EvalContext] = None, + ) -> t.Any: + """Invoke a filter on a value the same way the compiler does. + + This might return a coroutine if the filter is running from an + environment in async mode and the filter supports async + execution. It's your responsibility to await this if needed. + + .. versionadded:: 2.7 + """ + return self._filter_test_common( + name, value, args, kwargs, context, eval_ctx, True + ) + + def call_test( + self, + name: str, + value: t.Any, + args: t.Optional[t.Sequence[t.Any]] = None, + kwargs: t.Optional[t.Mapping[str, t.Any]] = None, + context: t.Optional[Context] = None, + eval_ctx: t.Optional[EvalContext] = None, + ) -> t.Any: + """Invoke a test on a value the same way the compiler does. + + This might return a coroutine if the test is running from an + environment in async mode and the test supports async execution. + It's your responsibility to await this if needed. + + .. versionchanged:: 3.0 + Tests support ``@pass_context``, etc. decorators. Added + the ``context`` and ``eval_ctx`` parameters. + + .. versionadded:: 2.7 + """ + return self._filter_test_common( + name, value, args, kwargs, context, eval_ctx, False + ) + + @internalcode + def parse( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> nodes.Template: + """Parse the sourcecode and return the abstract syntax tree. This + tree of nodes is used by the compiler to convert the template into + executable source- or bytecode. This is useful for debugging or to + extract information from templates. + + If you are :ref:`developing Jinja extensions ` + this gives you a good overview of the node tree generated. + """ + try: + return self._parse(source, name, filename) + except TemplateSyntaxError: + self.handle_exception(source=source) + + def _parse( + self, source: str, name: t.Optional[str], filename: t.Optional[str] + ) -> nodes.Template: + """Internal parsing function used by `parse` and `compile`.""" + return Parser(self, source, name, filename).parse() + + def lex( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> t.Iterator[t.Tuple[int, str, str]]: + """Lex the given sourcecode and return a generator that yields + tokens as tuples in the form ``(lineno, token_type, value)``. + This can be useful for :ref:`extension development ` + and debugging templates. + + This does not perform preprocessing. If you want the preprocessing + of the extensions to be applied you have to filter source through + the :meth:`preprocess` method. + """ + source = str(source) + try: + return self.lexer.tokeniter(source, name, filename) + except TemplateSyntaxError: + self.handle_exception(source=source) + + def preprocess( + self, + source: str, + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + ) -> str: + """Preprocesses the source with all extensions. This is automatically + called for all parsing and compiling methods but *not* for :meth:`lex` + because there you usually only want the actual source tokenized. + """ + return reduce( + lambda s, e: e.preprocess(s, name, filename), + self.iter_extensions(), + str(source), + ) + + def _tokenize( + self, + source: str, + name: t.Optional[str], + filename: t.Optional[str] = None, + state: t.Optional[str] = None, + ) -> TokenStream: + """Called by the parser to do the preprocessing and filtering + for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`. + """ + source = self.preprocess(source, name, filename) + stream = self.lexer.tokenize(source, name, filename, state) + + for ext in self.iter_extensions(): + stream = ext.filter_stream(stream) # type: ignore + + if not isinstance(stream, TokenStream): + stream = TokenStream(stream, name, filename) + + return stream + + def _generate( + self, + source: nodes.Template, + name: t.Optional[str], + filename: t.Optional[str], + defer_init: bool = False, + ) -> str: + """Internal hook that can be overridden to hook a different generate + method in. + + .. versionadded:: 2.5 + """ + return generate( # type: ignore + source, + self, + name, + filename, + defer_init=defer_init, + optimized=self.optimized, + ) + + def _compile(self, source: str, filename: str) -> CodeType: + """Internal hook that can be overridden to hook a different compile + method in. + + .. versionadded:: 2.5 + """ + return compile(source, filename, "exec") + + @typing.overload + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: "te.Literal[False]" = False, + defer_init: bool = False, + ) -> CodeType: ... + + @typing.overload + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: "te.Literal[True]" = ..., + defer_init: bool = False, + ) -> str: ... + + @internalcode + def compile( + self, + source: t.Union[str, nodes.Template], + name: t.Optional[str] = None, + filename: t.Optional[str] = None, + raw: bool = False, + defer_init: bool = False, + ) -> t.Union[str, CodeType]: + """Compile a node or template source code. The `name` parameter is + the load name of the template after it was joined using + :meth:`join_path` if necessary, not the filename on the file system. + the `filename` parameter is the estimated filename of the template on + the file system. If the template came from a database or memory this + can be omitted. + + The return value of this method is a python code object. If the `raw` + parameter is `True` the return value will be a string with python + code equivalent to the bytecode returned otherwise. This method is + mainly used internally. + + `defer_init` is use internally to aid the module code generator. This + causes the generated code to be able to import without the global + environment variable to be set. + + .. versionadded:: 2.4 + `defer_init` parameter added. + """ + source_hint = None + try: + if isinstance(source, str): + source_hint = source + source = self._parse(source, name, filename) + source = self._generate(source, name, filename, defer_init=defer_init) + if raw: + return source + if filename is None: + filename = "

pIm%2hG?$zmcsP1H9TCpgmRU*Y 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 bW9Z&{Tt?t{t-;J| literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/templating.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/templating.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..655bb48f2656ae56c74059c2746545c7ce37642d GIT binary patch literal 9939 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 F@W0zL=S~0s literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c152acdb687f1981646d05788e5211d2164f9bd3 GIT binary patch literal 13620 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 Qy7=d=zw;S~7en&@1EOW@n*aa+ literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/typing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2f4db1df5bc440b10dc737e57f3ca3dde849a64 GIT binary patch literal 4138 zcmbVOO-~!!8J@9^jXyBP_yaJ+kQ*QY5<5V+Byb4Vp0`V56u!#ZoNIQ5$rWrq#8HGi{iosJ6C5 zt=PKWqNg_ALL0=IIopOkYS&^B=h$#Yo!Z*MHE+10E}DYe?naKjVy)PXTd@bceY&mj z?t~?H1{xN91zCm}aZ@elk{;Q3WA_=pR?UajhZ^zvs+MQ$)AHhWH9HzG?h&|pYBk!i zn{BFcjGMKb4&xlwc52@p^&UrchK)A{1YC(@|%#wa)d@c|sHRaDnqC)eQ7ShDuG zMPu7-Qs*(*z|&W(gSGy_`ID)-kA@)XrtWLJt13i$Phn$SpKHYGD;!GDI8^WXvE1Y!-h@5^u@+DO0dMn4c?pv7s7&Z^g_ zS>xJ&YVCou?_DkbBIFy~b>ph1FuSC(O3-KPe|9c-KW{dJ5c%1BLE+C*0cw89d_Gb0 ze`YjfT%T&9SyyUx!^RPCT0YTN!lX|)8abtg@lDXbk#WX}>!3ybN!2(YK z!9#KBo_UTD)lkbCpGmQUV~S&SHJ*_&0$yDe%xb7R#&aBwX^>%NEvA^&2a2grtF)O; zrVz#3UaEPlLT;d6a^#6*Hs(+f50J3gVKYq?l(@n@ZDnOj@I2Tad88NW7r9 zX2s1^Oxvz;xY6a40&wb>rxA2T==!%A~a#H!t#Oi7AYmuwjW=~UIz zv;tlJzz9NEuOj6R8zS%g?T~=X5 zsU8rV!*O1~-zEi7`q<~M`aU)zHW`zYa}S?jj+^IG5`Hc{5cn7tMP8`abK0RyYWSeb| zmVb2{$C(V*XuTV3nzRoDknZCKe0Kza7w$1BmOFv%TIMrAxhC%c^)t+Z`sKHgC;SGE zFj*YA&BrnuI3k@TkYginoyM@A!J zQo>i#OzbI>z+xm0cjalMdgR0D&0lQaZ18M`!#4%>zcCvA#0j`v6*EoKN7PsUqC)?m z+CG@k%bDNL9HH&py5e#rVe3J?_76$$EU?7(-f;4mEjqTH>o z6R#4xvBI^YdGAIMm*x+>rF(C9IWSIIzJB2ZO0AyP*;m;za;Xqi22*lNh@hT2=I+D7 zuRxBpV;ISFCnqD+lTB!Dpd?J@9BM-E*bEVKNJNgM4sBXNnqZ>HYHU&<0 z3=uS3M*fN&S=x`y)^?Ljc`E_R6Wpcq_ME*CISl@-^_v&24?cf>F}GL{bN6M+QwET1 z4mfIZ?7DV-S9HnlD+Gm4n&`LAH_p;`q<2zwPZBf*!>~*ro6Q{n;;Y!GP|yD3y~lb} z!tS-5=LB^W&hHQH4HcJ5o_AdbU56`Va8mA{B*7^eO&yy}AxI8_N}a*|rac&0bsz!^ zX)-q{hen8ZRCbRNGB*O#BjP3T?Hc}LDsm?o`*vU{j>TXtV1 zXvEk#`)=uA={+Ksr)BpvK{r+CTPm~0T%u?Aj0R1y96wMr0~b1__PKFx#!% zR`EhnDh`tN%P_05Kah{+qD60!EsY)0f3p11QkoD9vU10;>>eiQazj$hUZD4#drrA;f^=M$-PZ}ap>_|fHnXC literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4495079d08bbc96f35fd9b69838e51307fb1e8df GIT binary patch literal 7029 zcmb_hZEPIJd7k~aIhPkh513K z9Lqs7G454dE1L?n2^NHU*@F>%l9^W6FljM|1uWFcl^{$6+$^#%Q8t6(WKk|h zmeJaj^#Iy82qR;PMaI-OLGmDLbxgN3maz0;7=>2SN1_J6wL{Hnz@3F z$tNmPphf)3qD7e5r#7?-kM5vAaciDM1sl_?(l_6DO|He{wmI%FKi4AKCmG1W@`^(R zT%}*2C6B|7Ef$!z1KXUafTSYYT#gn!iw%rBUh&+}5G_|>4p;IK@)FX1nK_QvcN(~5 zhr|>(gpcjofx$rd)mYN>1$xYMd{$?HbQUYojXLS`nN#Ygt~tdvaK$e;3o6{DrCu?2 zKRu6h7xwnn`GVo+Ov^GV+%b5?VSNv+tT#W2i_Q<@cZYiWg>ntl*b9PVE>dk@ub}JA z*1pchQVi&j83cT&AO#PC6snHZi6${=x)Y4fp!y>sp#))YC}Kmkjwn<3a22wp62#s*V| zC_~4=B_5{z3if*+5zeC~AiSke5N1%2*2hXEyJ$1FSna137fjU`^H;qJb(vhC55i!e zb`iz~CLC|vbf{$pCQ7|fz%>^NIq})(>=Qxm2wLW8lfMO3&vBUW#JBu_pP_Sf)N|RH zuqo2XY*z#MxaT=FiXCO{O@lWWW+m{ZAlMd-C_c{iHGG4bz)C(79O* zDj=1IgU>jdG^cHk=V%-qnNO!GXgg6<6hcF%5>XQ)P2^oBoi-gCn|>M649ij267)s! zgqFBBB`hZnHNrE&*Qg1)DuV(*ccxFzvFeP+p&ExXsVAg{WYjV01t+G;4#P;ttfQQJ z9J+N>sgJv*UmE#IZ;Iv38I~XMiZUeTxoMf)fHlBZu9cr*<02R{p3g!2oX$J;cwWY< zgSo+{^S&LhfwEaVXHGCbUjp+umlpvrSFVQb8>7$cHVkLEjoqWL96t@kJo&7JWO`S& z>|09hTZax(jFLRoNdo>ds5A~6xTIdvE+sA{Z;&~1Rt!H}@McR}P#3g?WJ$A>i)~ku zE`WkLa#^*>W$kCmkCZD3ph%PyOT|&((l}Sdj1SH!myGrc^SeSXA|Jej5Dj|6*4_? zuM)&r5e@gHDrPOS;soI8kH%XOI%{ach3PgV{Th3*770~wCXp@UI9E&inDuL4d?kpH z@hnGANLbOp*=LR4%e3`q+<7=M%YFHfAM0z!ZPnHR;;NvBKeFryJfN74zJnP~kQ54- zYYT;biU`UBp#kiOBSIW4L@EO8Cbv?X3gZb(Gh=eAp067AS11^U*pC4jB@#m&3IK`$ zJMaOD$&73Pl6gA!bd5VM^sps3gC+}wO(au@(IjMidt8_TeuYmlwmf{JvBRkZ93Lw0 zX*b|3aarHY>FdsYK~YIibIP+d_d<5^nNv1%<-3t zsyO;i*veeL!kOV$-6G!&J=sKA=9I-)|G7R)E!w6dpW6HB25gP{Y#i1s{0K^Y1 zJiYR5P-L4zt?2lnj-T)+v3oyKHrLRK*sV~skHM9KW6&306zUI~BBjr^HJZSiK<57v zia*bjwf&?mbFX#p2d#U*(Dn3pl{JFpgDs@9>t@SR$H7Hsx#Qr%$mhC(gGr$NheKBm zt?s4^BUit(x@Yfg_m&IQ*H?S!?YD2e{V7q>-?-NN-(7UE^T+RA*Vi;?ueFn1dlvMo znbq#y@Jg?C?YwdR`gy3ft?ul(ox7D2gAKje{(ISD%h_WK`iH6gtC^04R$*RQl|KdR z=KWBBOb6i=Y5*3cV7`e%C><_pNIBb%3c;KZqrPk#A3%~E6k(fTd=H?QR0q#vKT1B# z7)Han9rS^!5ZaS^M9+k?J7QNE#rQ!a*oy_afpoqOtYOg(fBp;t^jxi@S!L;Ebq?S5>B!F`?WT`mCMb`sx zp0fbm77E9Yo&xGF- zs~t2TM^-7MZ7S~`qN8QBDTE%j3z-nk;<&xq=`CXX|9`|k0vARAi(u`2bp>rWm*XiO z#3&B#8-{R^(V+so3MhQc#yOMt=0JI!P?ubsDI0j#mLu;Micv}j zw;=@cb}K@O*Ps`(Ts_BKPRx-X0#LBvgfORI>W2$V1+eiZ;9*+fKL~2*I6gF9P=Pun z=6R?9^bg_ff)>Ec@|lA(iACZ|NME_Sbxu8tN1zSQoVFPmHAy)@<5@5g*56lCB z@`4^<@DR@&oHybNP?)71X2IaN;hEFVV#3?-#Jg&k?EsusRGylHeb&zgRf4~)HY)QV zhSh9anD8rQ#>1@49Ys4!6}!&KQMKLArpOd;}uj(kH64$3%*gkOe_79gxNLL0-- zp50ydzuY4${y9+iH=zK$@o_4BxoJ7IcRBUc;;|1>Pu*|rym{pIYqwroZteSc`;H&8 zh2!_Tcim3kN`Fe!^x!r9%;hzqF)Olbn0VJQL zNOu;A2d-&Id~i|wsN)GFef9dQi`p-mf7<-7tsiyvBk{n46lw2T-Sgx%?Y-7D4eD!M zq%AAKT%(yIumodM)>Y_jH1atTAk-4iW`=#XX4EkiA+aIU4HgVtou_}Dlck*N19hb9 z4mJm_wSh zB@l4T?rJcpjW-^@0s?;;3NWb5_G`gU{sz(5y`I74p23x#ga3YNB{Q_3ucb&^`@--~ zj^7@=HF~G?!R}|IF>Sk}uXGG7jNH$3EM*>FO1-+Y@A?1i`pusI*t7KfD@%J{U4fp= zG&N=g)Gw0|ZTU%QsJmr6F&HnuXN%|F4+$U%k2glW%SlcCw{wc zGAvr#&>CYAueGJ@8*2YGbs3Ut5v@=#EX~Rq1#=qpmX|eshW((#ObfNv3$wGYPhL;6 zB6UDRQ~g_bhM7f-V!xJcQh`NN^ogWPoU?+ZYQ-XkbgO1Dm$lTnT!|HV7VSmN%HTs_ zrj%o8d#Uz2VE@Pu-T}!UgTqWp%DR;FeGi;Y`dJ_u)O{?Z`(H#yJ&K`rXkBBIvn)S{x+Mm5%|_pVCwvL1Uc`iZ?5DcQ!_7WV>>W*uio9z(kWVYHMGRh^1Qyg zp`}aj6Z`CWFPiMu`$hRVly|L4*X88ytJ09dL)fhLPmsiHhAG_E3B{OOWNAw&nM_ya z%8JhBwNlPfvIXVUoMtX16lI3xS$>XjQ?W9vMZuHTtaJwbSwSxvPyj_M=*qP$TUIo_ zP|8D9%tVXmIR#3lvYgQ@>TD{dqN16~L>i>ciPSsuVhU#|Da*Rndp_*_mg=82mMtt>lNve5d5LyAO@ zEuf*8E74r@V5?ZOAYv9-Iy;|DD+anGMAM_7l{i}{*CKRW)x4qO6i!znbQqNz$}~FG z!X=(lIT|Xv^=#VuJ=5YNiWv2Mw@Wk0Plf! zNjIcL_glm752HJ`UzIGM+s$mT90Zc2oAM3mkE4>*_!8f+g37~>CwZSsnj`JHAXK9)kvk33^)dM(n+O(Z(Kb;g(J&BU(pS)7iJ7F1bx*k zh8~)In2C+pn#FiNTfi5AN(vO3X?f^$h81Xwx@KvDA0!p*U%O^xvCRSq;&5fH`d;mk z&Uq)|lUh3C?ZSaWOk#*dP7vXV)aA2ps^_ju&rVLys+T6G&(B^+DNz0S?7|4ZE}PDH zTW=)b@(SXy1Y|*NNIpgZ3g8{=RLWr?SVSv_6v`esVw5dDu^`CJ8%O6iq0AEanHi&$ z1DxCBL4g}E)L>SwtjuXp5RV>BjsNSC9yAqb6464*qg{ZWx{yvhN+cAB$%G4$&S-@N zrcX>L#}h|AveZYcgn-#u2GDWTJrlNZcoL=Y#L4ZHuq~UzC9cf@*{lrkXdz=mqw~qe zLn2%2+%BS_An(G_=Hw36f@TQ|s&(!FS|NL$+rC=BWV!iTvuaxjRW%7&l$A$wV;LjQ z#KVk6^YiEFHIZ7i3a8!MKrj8Oomj*Xd6!qK9Z zUeXqrIVKF@m}f+aW!{UaD!ntdl}>D5=I`LUc?PeqR;0~gDcZgf*}oRqUyY32iSOQs zA6bhZsYZ`{9_zgu@A=@=T70w`9sMlUyAc~$iw#uwom`8Zs)kQJ00AlO0VXO`7Vz7N z0>BrrsiI^*W$CsIr&93owvRuL)>`nI21n3ljl%oU^tEe$6P-*F!-{l&|KZiZFT)B~ zQ2i~n13w2(W*h7P*)VdDY%8D@%0QgL=zJDhNv2Mj&oZ38yoSRj`e~jvr&4)1S6MQ8 zW7Kt&-~{J<&d|#Ow4Z7qJTlL0D3qNIgh!At;~5NvbLbji!!Z!`bNx+G^wldfm(|OY zm#@scrCyy%PJS~S{nq%nIzB$0g3I{Lq14;0fK&nJ|05{%=m|&_DE_|*rOya}T?Bwz zxeEyF#yJxZ$jQ&Cb?xM!@jcXOFJ2Df*~fYi4VAr(khIma`+XYXf>NIVrGsmcgVo4U z0ZOAb4AntttQs9_0HxDwu@|c07i=te22kSrFfU+9>}|idYzK|Le%U4saq#HiFyYa@ z-)20zwFQsreyKnn$1!#Ar^uNJkDT!8|8dB^-0tMfiIejB*_qj^-wc6{K0E&WA9v-R z5r5h|{1IFQ^6bJXYe1eh4|&8L@Clf61cNHOwqcGKvP{SJakS}Qi}Y6`M+DjouEhtd z(ZM>}3{|5;4QO+0Eq1&bKK|cE8?m2lX!EoM|49SR^dBIc*}ENQ=q@ZAunQXO6huN0DZ#X2KXfagXL`uT+rgDu*kuF^2RX7P zG2Ss52^1^suGYC7Kcrp*c){cDf^qxFnVV;-kpob+kIsB>W+Ohd7AFq}UB-OX&8}bi(bl(m6n#8+q$cr>;%cJ`~p@rhn{dS}R(hX>fj&wO&$eOquW7`Gd zh7)IUBDjhdv)w9(odirQb@xt}X}XZp!6Tw|TjG%%A+|#=NC01>8;YkBo-B9J4KE5% zU})LJ+(CM$yK_Pt27H{ObB&E7gI9j)M{_aX5W$~V0&Q!8%U!afq9lR)rs?^5V%QUu@aG>9O?+o(@ZEv%{pAgWqxQJuUD%28GQK}pLwUz$`^H`1!gn=yvR z@IrP(ZOGx`6S*tAgI=^tJBTT+y5xb@!W7+Q+_FQ4wGaY1u>YKHy_}p;VTf#O`xaH{ zEN~J|tlUc`46qO5o_%$oHkk<|?~WRzP(b~gfJ9p+8wto;?7 z65D9oseCuRD|~&}(Wj(&-!S-+3Cp`gv2{B?g3y8Zg8-Kbg4_9eo)T$GGR;olPD7(Gpk=#d$Y|I;8|DXHCoxTFSqj6$ zYfEcbzMwY&dyv_Z(amd1Om$bBvh#8l+HiS*4dZK&$w}taS%KJ`$20e@0FZryU2rn|B>h^<&S-gSfH5P#&5oOhSZ-_!kGDr~zl#Bt zo+mBj-|^KXy8&zbWt$Y;eK+2>5kI&VKUh744ASXp^z_4!)ZTq_c`bIR8a^c8&9RF$ zKmHbAD9T@S}$*EGE-n$ck7_Gl%`NE`UOqBQ8tI^Cfr*Fx8y=###v8CQe07wrcSm(($%4M7~w6!~5*YS>Syw;y6Ap zBi$<2T8p}Dr>2_Ci(1n{&Y073rtR-Gxh^eTLTatEZ@oULp1Ux4?&8$+`C7X-lk timedelta | None: + if value is None or isinstance(value, timedelta): + return value + + return timedelta(seconds=value) + + +class Flask(App): + """The flask object implements a WSGI application and acts as the central + object. It is passed the name of the module or package of the + application. Once it is created it will act as a central registry for + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the :file:`__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea of what + belongs to your application. This name is used to find resources + on the filesystem, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in :file:`yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplication.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. + + .. versionadded:: 0.11 + The `root_path` parameter was added. + + .. versionadded:: 1.0 + The ``host_matching`` and ``static_host`` parameters were added. + + .. versionadded:: 1.0 + The ``subdomain_matching`` parameter was added. Subdomain + matching needs to be enabled manually now. Setting + :data:`SERVER_NAME` does not implicitly enable it. + + :param import_name: the name of the application package + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: The folder with static files that is served at + ``static_url_path``. Relative to the application ``root_path`` + or an absolute path. Defaults to ``'static'``. + :param static_host: the host to use when adding the static route. + Defaults to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param host_matching: set ``url_map.host_matching`` attribute. + Defaults to False. + :param subdomain_matching: consider the subdomain relative to + :data:`SERVER_NAME` when matching routes. Defaults to False. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to ``True`` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. + :param root_path: The path to the root of the application files. + This should only be set manually when it can't be detected + automatically, such as for namespace packages. + """ + + default_config = ImmutableDict( + { + "DEBUG": None, + "TESTING": False, + "PROPAGATE_EXCEPTIONS": None, + "SECRET_KEY": None, + "SECRET_KEY_FALLBACKS": None, + "PERMANENT_SESSION_LIFETIME": timedelta(days=31), + "USE_X_SENDFILE": False, + "TRUSTED_HOSTS": None, + "SERVER_NAME": None, + "APPLICATION_ROOT": "/", + "SESSION_COOKIE_NAME": "session", + "SESSION_COOKIE_DOMAIN": None, + "SESSION_COOKIE_PATH": None, + "SESSION_COOKIE_HTTPONLY": True, + "SESSION_COOKIE_SECURE": False, + "SESSION_COOKIE_PARTITIONED": False, + "SESSION_COOKIE_SAMESITE": None, + "SESSION_REFRESH_EACH_REQUEST": True, + "MAX_CONTENT_LENGTH": None, + "MAX_FORM_MEMORY_SIZE": 500_000, + "MAX_FORM_PARTS": 1_000, + "SEND_FILE_MAX_AGE_DEFAULT": None, + "TRAP_BAD_REQUEST_ERRORS": None, + "TRAP_HTTP_EXCEPTIONS": False, + "EXPLAIN_TEMPLATE_LOADING": False, + "PREFERRED_URL_SCHEME": "http", + "TEMPLATES_AUTO_RELOAD": None, + "MAX_COOKIE_SIZE": 4093, + "PROVIDE_AUTOMATIC_OPTIONS": True, + } + ) + + #: The class that is used for request objects. See :class:`~flask.Request` + #: for more information. + request_class: type[Request] = Request + + #: The class that is used for response objects. See + #: :class:`~flask.Response` for more information. + response_class: type[Response] = Response + + #: the session interface to use. By default an instance of + #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. + #: + #: .. versionadded:: 0.8 + session_interface: SessionInterface = SecureCookieSessionInterface() + + def __init__( + self, + import_name: str, + static_url_path: str | None = None, + static_folder: str | os.PathLike[str] | None = "static", + static_host: str | None = None, + host_matching: bool = False, + subdomain_matching: bool = False, + template_folder: str | os.PathLike[str] | None = "templates", + instance_path: str | None = None, + instance_relative_config: bool = False, + root_path: str | None = None, + ): + super().__init__( + import_name=import_name, + static_url_path=static_url_path, + static_folder=static_folder, + static_host=static_host, + host_matching=host_matching, + subdomain_matching=subdomain_matching, + template_folder=template_folder, + instance_path=instance_path, + instance_relative_config=instance_relative_config, + root_path=root_path, + ) + + #: The Click command group for registering CLI commands for this + #: object. The commands are available from the ``flask`` command + #: once the application has been discovered and blueprints have + #: been registered. + self.cli = cli.AppGroup() + + # Set the name of the Click group in case someone wants to add + # the app's commands to another CLI tool. + self.cli.name = self.name + + # Add a static route using the provided static_url_path, static_host, + # and static_folder if there is a configured static_folder. + # Note we do this without checking if static_folder exists. + # For one, it might be created while the server is running (e.g. during + # development). Also, Google App Engine stores static files somewhere + if self.has_static_folder: + assert bool(static_host) == host_matching, ( + "Invalid static_host/host_matching combination" + ) + # Use a weakref to avoid creating a reference cycle between the app + # and the view function (see #3761). + self_ref = weakref.ref(self) + self.add_url_rule( + f"{self.static_url_path}/", + endpoint="static", + host=static_host, + view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950 + ) + + def get_send_file_max_age(self, filename: str | None) -> int | None: + """Used by :func:`send_file` to determine the ``max_age`` cache + value for a given file path if it wasn't passed. + + By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from + the configuration of :data:`~flask.current_app`. This defaults + to ``None``, which tells the browser to use conditional requests + instead of a timed cache, which is usually preferable. + + Note this is a duplicate of the same method in the Flask + class. + + .. versionchanged:: 2.0 + The default configuration is ``None`` instead of 12 hours. + + .. versionadded:: 0.9 + """ + value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] + + if value is None: + return None + + if isinstance(value, timedelta): + return int(value.total_seconds()) + + return value # type: ignore[no-any-return] + + def send_static_file(self, filename: str) -> Response: + """The view function used to serve files from + :attr:`static_folder`. A route is automatically registered for + this view at :attr:`static_url_path` if :attr:`static_folder` is + set. + + Note this is a duplicate of the same method in the Flask + class. + + .. versionadded:: 0.5 + + """ + if not self.has_static_folder: + raise RuntimeError("'static_folder' must be set to serve static_files.") + + # send_file only knows to call get_send_file_max_age on the app, + # call it here so it works for blueprints too. + max_age = self.get_send_file_max_age(filename) + return send_from_directory( + t.cast(str, self.static_folder), filename, max_age=max_age + ) + + def open_resource( + self, resource: str, mode: str = "rb", encoding: str | None = None + ) -> t.IO[t.AnyStr]: + """Open a resource file relative to :attr:`root_path` for reading. + + For example, if the file ``schema.sql`` is next to the file + ``app.py`` where the ``Flask`` app is defined, it can be opened + with: + + .. code-block:: python + + with app.open_resource("schema.sql") as f: + conn.executescript(f.read()) + + :param resource: Path to the resource relative to :attr:`root_path`. + :param mode: Open the file in this mode. Only reading is supported, + valid values are ``"r"`` (or ``"rt"``) and ``"rb"``. + :param encoding: Open the file with this encoding when opening in text + mode. This is ignored when opening in binary mode. + + .. versionchanged:: 3.1 + Added the ``encoding`` parameter. + """ + if mode not in {"r", "rt", "rb"}: + raise ValueError("Resources can only be opened for reading.") + + path = os.path.join(self.root_path, resource) + + if mode == "rb": + return open(path, mode) # pyright: ignore + + return open(path, mode, encoding=encoding) + + def open_instance_resource( + self, resource: str, mode: str = "rb", encoding: str | None = "utf-8" + ) -> t.IO[t.AnyStr]: + """Open a resource file relative to the application's instance folder + :attr:`instance_path`. Unlike :meth:`open_resource`, files in the + instance folder can be opened for writing. + + :param resource: Path to the resource relative to :attr:`instance_path`. + :param mode: Open the file in this mode. + :param encoding: Open the file with this encoding when opening in text + mode. This is ignored when opening in binary mode. + + .. versionchanged:: 3.1 + Added the ``encoding`` parameter. + """ + path = os.path.join(self.instance_path, resource) + + if "b" in mode: + return open(path, mode) + + return open(path, mode, encoding=encoding) + + def create_jinja_environment(self) -> Environment: + """Create the Jinja environment based on :attr:`jinja_options` + and the various Jinja-related methods of the app. Changing + :attr:`jinja_options` after this will have no effect. Also adds + Flask-related globals and filters to the environment. + + .. versionchanged:: 0.11 + ``Environment.auto_reload`` set in accordance with + ``TEMPLATES_AUTO_RELOAD`` configuration option. + + .. versionadded:: 0.5 + """ + options = dict(self.jinja_options) + + if "autoescape" not in options: + options["autoescape"] = self.select_jinja_autoescape + + if "auto_reload" not in options: + auto_reload = self.config["TEMPLATES_AUTO_RELOAD"] + + if auto_reload is None: + auto_reload = self.debug + + options["auto_reload"] = auto_reload + + rv = self.jinja_environment(self, **options) + rv.globals.update( + url_for=self.url_for, + get_flashed_messages=get_flashed_messages, + config=self.config, + # request, session and g are normally added with the + # context processor for efficiency reasons but for imported + # templates we also want the proxies in there. + request=request, + session=session, + g=g, + ) + rv.policies["json.dumps_function"] = self.json.dumps + return rv + + def create_url_adapter(self, request: Request | None) -> MapAdapter | None: + """Creates a URL adapter for the given request. The URL adapter + is created at a point where the request context is not yet set + up so the request is passed explicitly. + + .. versionchanged:: 3.1 + If :data:`SERVER_NAME` is set, it does not restrict requests to + only that domain, for both ``subdomain_matching`` and + ``host_matching``. + + .. versionchanged:: 1.0 + :data:`SERVER_NAME` no longer implicitly enables subdomain + matching. Use :attr:`subdomain_matching` instead. + + .. versionchanged:: 0.9 + This can be called outside a request when the URL adapter is created + for an application context. + + .. versionadded:: 0.6 + """ + if request is not None: + if (trusted_hosts := self.config["TRUSTED_HOSTS"]) is not None: + request.trusted_hosts = trusted_hosts + + # Check trusted_hosts here until bind_to_environ does. + request.host = get_host(request.environ, request.trusted_hosts) # pyright: ignore + subdomain = None + server_name = self.config["SERVER_NAME"] + + if self.url_map.host_matching: + # Don't pass SERVER_NAME, otherwise it's used and the actual + # host is ignored, which breaks host matching. + server_name = None + elif not self.subdomain_matching: + # Werkzeug doesn't implement subdomain matching yet. Until then, + # disable it by forcing the current subdomain to the default, or + # the empty string. + subdomain = self.url_map.default_subdomain or "" + + return self.url_map.bind_to_environ( + request.environ, server_name=server_name, subdomain=subdomain + ) + + # Need at least SERVER_NAME to match/build outside a request. + if self.config["SERVER_NAME"] is not None: + return self.url_map.bind( + self.config["SERVER_NAME"], + script_name=self.config["APPLICATION_ROOT"], + url_scheme=self.config["PREFERRED_URL_SCHEME"], + ) + + return None + + def raise_routing_exception(self, request: Request) -> t.NoReturn: + """Intercept routing exceptions and possibly do something else. + + In debug mode, intercept a routing redirect and replace it with + an error if the body will be discarded. + + With modern Werkzeug this shouldn't occur, since it now uses a + 308 status which tells the browser to resend the method and + body. + + .. versionchanged:: 2.1 + Don't intercept 307 and 308 redirects. + + :meta private: + :internal: + """ + if ( + not self.debug + or not isinstance(request.routing_exception, RequestRedirect) + or request.routing_exception.code in {307, 308} + or request.method in {"GET", "HEAD", "OPTIONS"} + ): + raise request.routing_exception # type: ignore[misc] + + from .debughelpers import FormDataRoutingRedirect + + raise FormDataRoutingRedirect(request) + + def update_template_context(self, context: dict[str, t.Any]) -> None: + """Update the template context with some commonly used variables. + This injects request, session, config and g into the template + context as well as everything template context processors want + to inject. Note that the as of Flask 0.6, the original values + in the context will not be overridden if a context processor + decides to return a value with the same key. + + :param context: the context as a dictionary that is updated in place + to add extra variables. + """ + names: t.Iterable[str | None] = (None,) + + # A template may be rendered outside a request context. + if request: + names = chain(names, reversed(request.blueprints)) + + # The values passed to render_template take precedence. Keep a + # copy to re-apply after all context functions. + orig_ctx = context.copy() + + for name in names: + if name in self.template_context_processors: + for func in self.template_context_processors[name]: + context.update(self.ensure_sync(func)()) + + context.update(orig_ctx) + + def make_shell_context(self) -> dict[str, t.Any]: + """Returns the shell context for an interactive shell for this + application. This runs all the registered shell context + processors. + + .. versionadded:: 0.11 + """ + rv = {"app": self, "g": g} + for processor in self.shell_context_processors: + rv.update(processor()) + return rv + + def run( + self, + host: str | None = None, + port: int | None = None, + debug: bool | None = None, + load_dotenv: bool = True, + **options: t.Any, + ) -> None: + """Runs the application on a local development server. + + Do not use ``run()`` in a production setting. It is not intended to + meet security and performance requirements for a production server. + Instead, see :doc:`/deploying/index` for WSGI server recommendations. + + If the :attr:`debug` flag is set the server will automatically reload + for code changes and show a debugger in case an exception happened. + + If you want to run the application in debug mode, but disable the + code execution on the interactive debugger, you can pass + ``use_evalex=False`` as parameter. This will keep the debugger's + traceback screen active, but disable code execution. + + It is not recommended to use this function for development with + automatic reloading as this is badly supported. Instead you should + be using the :command:`flask` command line script's ``run`` support. + + .. admonition:: Keep in Mind + + Flask will suppress any server error with a generic error page + unless it is in debug mode. As such to enable just the + interactive debugger without the code reloading, you have to + invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``. + Setting ``use_debugger`` to ``True`` without being in debug mode + won't catch any exceptions because there won't be any to + catch. + + :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to + have the server available externally as well. Defaults to + ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable + if present. + :param port: the port of the webserver. Defaults to ``5000`` or the + port defined in the ``SERVER_NAME`` config variable if present. + :param debug: if given, enable or disable debug mode. See + :attr:`debug`. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param options: the options to be forwarded to the underlying Werkzeug + server. See :func:`werkzeug.serving.run_simple` for more + information. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment + variables from :file:`.env` and :file:`.flaskenv` files. + + The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`. + + Threaded mode is enabled by default. + + .. versionchanged:: 0.10 + The default port is now picked from the ``SERVER_NAME`` + variable. + """ + # Ignore this call so that it doesn't start another server if + # the 'flask run' command is used. + if os.environ.get("FLASK_RUN_FROM_CLI") == "true": + if not is_running_from_reloader(): + click.secho( + " * Ignoring a call to 'app.run()' that would block" + " the current 'flask' CLI command.\n" + " Only call 'app.run()' in an 'if __name__ ==" + ' "__main__"\' guard.', + fg="red", + ) + + return + + if get_load_dotenv(load_dotenv): + cli.load_dotenv() + + # if set, env var overrides existing value + if "FLASK_DEBUG" in os.environ: + self.debug = get_debug_flag() + + # debug passed to method overrides all other sources + if debug is not None: + self.debug = bool(debug) + + server_name = self.config.get("SERVER_NAME") + sn_host = sn_port = None + + if server_name: + sn_host, _, sn_port = server_name.partition(":") + + if not host: + if sn_host: + host = sn_host + else: + host = "127.0.0.1" + + if port or port == 0: + port = int(port) + elif sn_port: + port = int(sn_port) + else: + port = 5000 + + options.setdefault("use_reloader", self.debug) + options.setdefault("use_debugger", self.debug) + options.setdefault("threaded", True) + + cli.show_server_banner(self.debug, self.name) + + from werkzeug.serving import run_simple + + try: + run_simple(t.cast(str, host), port, self, **options) + finally: + # reset the first request information if the development server + # reset normally. This makes it possible to restart the server + # without reloader and that stuff from an interactive shell. + self._got_first_request = False + + def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient: + """Creates a test client for this application. For information + about unit testing head over to :doc:`/testing`. + + Note that if you are testing for assertions or exceptions in your + application code, you must set ``app.testing = True`` in order for the + exceptions to propagate to the test client. Otherwise, the exception + will be handled by the application (not visible to the test client) and + the only indication of an AssertionError or other exception will be a + 500 status code response to the test client. See the :attr:`testing` + attribute. For example:: + + app.testing = True + client = app.test_client() + + The test client can be used in a ``with`` block to defer the closing down + of the context until the end of the ``with`` block. This is useful if + you want to access the context locals for testing:: + + with app.test_client() as c: + rv = c.get('/?vodka=42') + assert request.args['vodka'] == '42' + + Additionally, you may pass optional keyword arguments that will then + be passed to the application's :attr:`test_client_class` constructor. + For example:: + + from flask.testing import FlaskClient + + class CustomClient(FlaskClient): + def __init__(self, *args, **kwargs): + self._authentication = kwargs.pop("authentication") + super(CustomClient,self).__init__( *args, **kwargs) + + app.test_client_class = CustomClient + client = app.test_client(authentication='Basic ....') + + See :class:`~flask.testing.FlaskClient` for more information. + + .. versionchanged:: 0.4 + added support for ``with`` block usage for the client. + + .. versionadded:: 0.7 + The `use_cookies` parameter was added as well as the ability + to override the client to be used by setting the + :attr:`test_client_class` attribute. + + .. versionchanged:: 0.11 + Added `**kwargs` to support passing additional keyword arguments to + the constructor of :attr:`test_client_class`. + """ + cls = self.test_client_class + if cls is None: + from .testing import FlaskClient as cls + return cls( # type: ignore + self, self.response_class, use_cookies=use_cookies, **kwargs + ) + + def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner: + """Create a CLI runner for testing CLI commands. + See :ref:`testing-cli`. + + Returns an instance of :attr:`test_cli_runner_class`, by default + :class:`~flask.testing.FlaskCliRunner`. The Flask app object is + passed as the first argument. + + .. versionadded:: 1.0 + """ + cls = self.test_cli_runner_class + + if cls is None: + from .testing import FlaskCliRunner as cls + + return cls(self, **kwargs) # type: ignore + + def handle_http_exception( + self, e: HTTPException + ) -> HTTPException | ft.ResponseReturnValue: + """Handles an HTTP exception. By default this will invoke the + registered error handlers and fall back to returning the + exception as response. + + .. versionchanged:: 1.0.3 + ``RoutingException``, used internally for actions such as + slash redirects during routing, is not passed to error + handlers. + + .. versionchanged:: 1.0 + Exceptions are looked up by code *and* by MRO, so + ``HTTPException`` subclasses can be handled with a catch-all + handler for the base ``HTTPException``. + + .. versionadded:: 0.3 + """ + # Proxy exceptions don't have error codes. We want to always return + # those unchanged as errors + if e.code is None: + return e + + # RoutingExceptions are used internally to trigger routing + # actions, such as slash redirects raising RequestRedirect. They + # are not raised or handled in user code. + if isinstance(e, RoutingException): + return e + + handler = self._find_error_handler(e, request.blueprints) + if handler is None: + return e + return self.ensure_sync(handler)(e) # type: ignore[no-any-return] + + def handle_user_exception( + self, e: Exception + ) -> HTTPException | ft.ResponseReturnValue: + """This method is called whenever an exception occurs that + should be handled. A special case is :class:`~werkzeug + .exceptions.HTTPException` which is forwarded to the + :meth:`handle_http_exception` method. This function will either + return a response value or reraise the exception with the same + traceback. + + .. versionchanged:: 1.0 + Key errors raised from request data like ``form`` show the + bad key in debug mode rather than a generic bad request + message. + + .. versionadded:: 0.7 + """ + if isinstance(e, BadRequestKeyError) and ( + self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"] + ): + e.show_exception = True + + if isinstance(e, HTTPException) and not self.trap_http_exception(e): + return self.handle_http_exception(e) + + handler = self._find_error_handler(e, request.blueprints) + + if handler is None: + raise + + return self.ensure_sync(handler)(e) # type: ignore[no-any-return] + + def handle_exception(self, e: Exception) -> Response: + """Handle an exception that did not have an error handler + associated with it, or that was raised from an error handler. + This always causes a 500 ``InternalServerError``. + + Always sends the :data:`got_request_exception` signal. + + If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug + mode, the error will be re-raised so that the debugger can + display it. Otherwise, the original exception is logged, and + an :exc:`~werkzeug.exceptions.InternalServerError` is returned. + + If an error handler is registered for ``InternalServerError`` or + ``500``, it will be used. For consistency, the handler will + always receive the ``InternalServerError``. The original + unhandled exception is available as ``e.original_exception``. + + .. versionchanged:: 1.1.0 + Always passes the ``InternalServerError`` instance to the + handler, setting ``original_exception`` to the unhandled + error. + + .. versionchanged:: 1.1.0 + ``after_request`` functions and other finalization is done + even for the default 500 response when there is no handler. + + .. versionadded:: 0.3 + """ + exc_info = sys.exc_info() + got_request_exception.send(self, _async_wrapper=self.ensure_sync, exception=e) + propagate = self.config["PROPAGATE_EXCEPTIONS"] + + if propagate is None: + propagate = self.testing or self.debug + + if propagate: + # Re-raise if called with an active exception, otherwise + # raise the passed in exception. + if exc_info[1] is e: + raise + + raise e + + self.log_exception(exc_info) + server_error: InternalServerError | ft.ResponseReturnValue + server_error = InternalServerError(original_exception=e) + handler = self._find_error_handler(server_error, request.blueprints) + + if handler is not None: + server_error = self.ensure_sync(handler)(server_error) + + return self.finalize_request(server_error, from_error_handler=True) + + def log_exception( + self, + exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]), + ) -> None: + """Logs an exception. This is called by :meth:`handle_exception` + if debugging is disabled and right before the handler is called. + The default implementation logs the exception as error on the + :attr:`logger`. + + .. versionadded:: 0.8 + """ + self.logger.error( + f"Exception on {request.path} [{request.method}]", exc_info=exc_info + ) + + def dispatch_request(self) -> ft.ResponseReturnValue: + """Does the request dispatching. Matches the URL and returns the + return value of the view or error handler. This does not have to + be a response object. In order to convert the return value to a + proper response object, call :func:`make_response`. + + .. versionchanged:: 0.7 + This no longer does the exception handling, this code was + moved to the new :meth:`full_dispatch_request`. + """ + req = request_ctx.request + if req.routing_exception is not None: + self.raise_routing_exception(req) + rule: Rule = req.url_rule # type: ignore[assignment] + # if we provide automatic options for this URL and the + # request came with the OPTIONS method, reply automatically + if ( + getattr(rule, "provide_automatic_options", False) + and req.method == "OPTIONS" + ): + return self.make_default_options_response() + # otherwise dispatch to the handler for that endpoint + view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment] + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return] + + def full_dispatch_request(self) -> Response: + """Dispatches the request and on top of that performs request + pre and postprocessing as well as HTTP exception catching and + error handling. + + .. versionadded:: 0.7 + """ + self._got_first_request = True + + try: + request_started.send(self, _async_wrapper=self.ensure_sync) + rv = self.preprocess_request() + if rv is None: + rv = self.dispatch_request() + except Exception as e: + rv = self.handle_user_exception(e) + return self.finalize_request(rv) + + def finalize_request( + self, + rv: ft.ResponseReturnValue | HTTPException, + from_error_handler: bool = False, + ) -> Response: + """Given the return value from a view function this finalizes + the request by converting it into a response and invoking the + postprocessing functions. This is invoked for both normal + request dispatching as well as error handlers. + + Because this means that it might be called as a result of a + failure a special safe mode is available which can be enabled + with the `from_error_handler` flag. If enabled, failures in + response processing will be logged and otherwise ignored. + + :internal: + """ + response = self.make_response(rv) + try: + response = self.process_response(response) + request_finished.send( + self, _async_wrapper=self.ensure_sync, response=response + ) + except Exception: + if not from_error_handler: + raise + self.logger.exception( + "Request finalizing failed with an error while handling an error" + ) + return response + + def make_default_options_response(self) -> Response: + """This method is called to create the default ``OPTIONS`` response. + This can be changed through subclassing to change the default + behavior of ``OPTIONS`` responses. + + .. versionadded:: 0.7 + """ + adapter = request_ctx.url_adapter + methods = adapter.allowed_methods() # type: ignore[union-attr] + rv = self.response_class() + rv.allow.update(methods) + return rv + + def ensure_sync(self, func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Ensure that the function is synchronous for WSGI workers. + Plain ``def`` functions are returned as-is. ``async def`` + functions are wrapped to run and wait for the response. + + Override this method to change how the app runs async views. + + .. versionadded:: 2.0 + """ + if iscoroutinefunction(func): + return self.async_to_sync(func) + + return func + + def async_to_sync( + self, func: t.Callable[..., t.Coroutine[t.Any, t.Any, t.Any]] + ) -> t.Callable[..., t.Any]: + """Return a sync function that will run the coroutine function. + + .. code-block:: python + + result = app.async_to_sync(func)(*args, **kwargs) + + Override this method to change how the app converts async code + to be synchronously callable. + + .. versionadded:: 2.0 + """ + try: + from asgiref.sync import async_to_sync as asgiref_async_to_sync + except ImportError: + raise RuntimeError( + "Install Flask with the 'async' extra in order to use async views." + ) from None + + return asgiref_async_to_sync(func) + + def url_for( + self, + /, + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, + ) -> str: + """Generate a URL to the given endpoint with the given values. + + This is called by :func:`flask.url_for`, and can be called + directly as well. + + An *endpoint* is the name of a URL rule, usually added with + :meth:`@app.route() `, and usually the same name as the + view function. A route defined in a :class:`~flask.Blueprint` + will prepend the blueprint's name separated by a ``.`` to the + endpoint. + + In some cases, such as email messages, you want URLs to include + the scheme and domain, like ``https://example.com/hello``. When + not in an active request, URLs will be external by default, but + this requires setting :data:`SERVER_NAME` so Flask knows what + domain to use. :data:`APPLICATION_ROOT` and + :data:`PREFERRED_URL_SCHEME` should also be configured as + needed. This config is only used when not in an active request. + + Functions can be decorated with :meth:`url_defaults` to modify + keyword arguments before the URL is built. + + If building fails for some reason, such as an unknown endpoint + or incorrect values, the app's :meth:`handle_url_build_error` + method is called. If that returns a string, that is returned, + otherwise a :exc:`~werkzeug.routing.BuildError` is raised. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it + is external. + :param _external: If given, prefer the URL to be internal + (False) or require it to be external (True). External URLs + include the scheme and domain. When not in an active + request, URLs are external by default. + :param values: Values to use for the variable parts of the URL + rule. Unknown keys are appended as query string arguments, + like ``?a=b&c=d``. + + .. versionadded:: 2.2 + Moved from ``flask.url_for``, which calls this method. + """ + req_ctx = _cv_request.get(None) + + if req_ctx is not None: + url_adapter = req_ctx.url_adapter + blueprint_name = req_ctx.request.blueprint + + # If the endpoint starts with "." and the request matches a + # blueprint, the endpoint is relative to the blueprint. + if endpoint[:1] == ".": + if blueprint_name is not None: + endpoint = f"{blueprint_name}{endpoint}" + else: + endpoint = endpoint[1:] + + # When in a request, generate a URL without scheme and + # domain by default, unless a scheme is given. + if _external is None: + _external = _scheme is not None + else: + app_ctx = _cv_app.get(None) + + # If called by helpers.url_for, an app context is active, + # use its url_adapter. Otherwise, app.url_for was called + # directly, build an adapter. + if app_ctx is not None: + url_adapter = app_ctx.url_adapter + else: + url_adapter = self.create_url_adapter(None) + + if url_adapter is None: + raise RuntimeError( + "Unable to build URLs outside an active request" + " without 'SERVER_NAME' configured. Also configure" + " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as" + " needed." + ) + + # When outside a request, generate a URL with scheme and + # domain by default. + if _external is None: + _external = True + + # It is an error to set _scheme when _external=False, in order + # to avoid accidental insecure URLs. + if _scheme is not None and not _external: + raise ValueError("When specifying '_scheme', '_external' must be True.") + + self.inject_url_defaults(endpoint, values) + + try: + rv = url_adapter.build( # type: ignore[union-attr] + endpoint, + values, + method=_method, + url_scheme=_scheme, + force_external=_external, + ) + except BuildError as error: + values.update( + _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external + ) + return self.handle_url_build_error(error, endpoint, values) + + if _anchor is not None: + _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@") + rv = f"{rv}#{_anchor}" + + return rv + + def make_response(self, rv: ft.ResponseReturnValue) -> Response: + """Convert the return value from a view function to an instance of + :attr:`response_class`. + + :param rv: the return value from the view function. The view function + must return a response. Returning ``None``, or the view ending + without returning, is not allowed. The following types are allowed + for ``view_rv``: + + ``str`` + A response object is created with the string encoded to UTF-8 + as the body. + + ``bytes`` + A response object is created with the bytes as the body. + + ``dict`` + A dictionary that will be jsonify'd before being returned. + + ``list`` + A list that will be jsonify'd before being returned. + + ``generator`` or ``iterator`` + A generator that returns ``str`` or ``bytes`` to be + streamed as the response. + + ``tuple`` + Either ``(body, status, headers)``, ``(body, status)``, or + ``(body, headers)``, where ``body`` is any of the other types + allowed here, ``status`` is a string or an integer, and + ``headers`` is a dictionary or a list of ``(key, value)`` + tuples. If ``body`` is a :attr:`response_class` instance, + ``status`` overwrites the exiting value and ``headers`` are + extended. + + :attr:`response_class` + The object is returned unchanged. + + other :class:`~werkzeug.wrappers.Response` class + The object is coerced to :attr:`response_class`. + + :func:`callable` + The function is called as a WSGI application. The result is + used to create a response object. + + .. versionchanged:: 2.2 + A generator will be converted to a streaming response. + A list will be converted to a JSON response. + + .. versionchanged:: 1.1 + A dict will be converted to a JSON response. + + .. versionchanged:: 0.9 + Previously a tuple was interpreted as the arguments for the + response object. + """ + + status: int | None = None + headers: HeadersValue | None = None + + # unpack tuple returns + if isinstance(rv, tuple): + len_rv = len(rv) + + # a 3-tuple is unpacked directly + if len_rv == 3: + rv, status, headers = rv # type: ignore[misc] + # decide if a 2-tuple has status or headers + elif len_rv == 2: + if isinstance(rv[1], (Headers, dict, tuple, list)): + rv, headers = rv # pyright: ignore + else: + rv, status = rv # type: ignore[assignment,misc] + # other sized tuples are not allowed + else: + raise TypeError( + "The view function did not return a valid response tuple." + " The tuple must have the form (body, status, headers)," + " (body, status), or (body, headers)." + ) + + # the body must not be None + if rv is None: + raise TypeError( + f"The view function for {request.endpoint!r} did not" + " return a valid response. The function either returned" + " None or ended without a return statement." + ) + + # make sure the body is an instance of the response class + if not isinstance(rv, self.response_class): + if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, cabc.Iterator): + # let the response class set the status and headers instead of + # waiting to do it manually, so that the class can handle any + # special logic + rv = self.response_class( + rv, # pyright: ignore + status=status, + headers=headers, # type: ignore[arg-type] + ) + status = headers = None + elif isinstance(rv, (dict, list)): + rv = self.json.response(rv) + elif isinstance(rv, BaseResponse) or callable(rv): + # evaluate a WSGI callable, or coerce a different response + # class to the correct type + try: + rv = self.response_class.force_type( + rv, # type: ignore[arg-type] + request.environ, + ) + except TypeError as e: + raise TypeError( + f"{e}\nThe view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it" + f" was a {type(rv).__name__}." + ).with_traceback(sys.exc_info()[2]) from None + else: + raise TypeError( + "The view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it was a" + f" {type(rv).__name__}." + ) + + rv = t.cast(Response, rv) + # prefer the status if it was provided + if status is not None: + if isinstance(status, (str, bytes, bytearray)): + rv.status = status + else: + rv.status_code = status + + # extend existing headers with provided headers + if headers: + rv.headers.update(headers) + + return rv + + def preprocess_request(self) -> ft.ResponseReturnValue | None: + """Called before the request is dispatched. Calls + :attr:`url_value_preprocessors` registered with the app and the + current blueprint (if any). Then calls :attr:`before_request_funcs` + registered with the app and the blueprint. + + If any :meth:`before_request` handler returns a non-None value, the + value is handled as if it was the return value from the view, and + further request handling is stopped. + """ + names = (None, *reversed(request.blueprints)) + + for name in names: + if name in self.url_value_preprocessors: + for url_func in self.url_value_preprocessors[name]: + url_func(request.endpoint, request.view_args) + + for name in names: + if name in self.before_request_funcs: + for before_func in self.before_request_funcs[name]: + rv = self.ensure_sync(before_func)() + + if rv is not None: + return rv # type: ignore[no-any-return] + + return None + + def process_response(self, response: Response) -> Response: + """Can be overridden in order to modify the response object + before it's sent to the WSGI server. By default this will + call all the :meth:`after_request` decorated functions. + + .. versionchanged:: 0.5 + As of Flask 0.5 the functions registered for after request + execution are called in reverse order of registration. + + :param response: a :attr:`response_class` object. + :return: a new response object or the same, has to be an + instance of :attr:`response_class`. + """ + ctx = request_ctx._get_current_object() # type: ignore[attr-defined] + + for func in ctx._after_request_functions: + response = self.ensure_sync(func)(response) + + for name in chain(request.blueprints, (None,)): + if name in self.after_request_funcs: + for func in reversed(self.after_request_funcs[name]): + response = self.ensure_sync(func)(response) + + if not self.session_interface.is_null_session(ctx.session): + self.session_interface.save_session(self, ctx.session, response) + + return response + + def do_teardown_request( + self, + exc: BaseException | None = _sentinel, # type: ignore[assignment] + ) -> None: + """Called after the request is dispatched and the response is + returned, right before the request context is popped. + + This calls all functions decorated with + :meth:`teardown_request`, and :meth:`Blueprint.teardown_request` + if a blueprint handled the request. Finally, the + :data:`request_tearing_down` signal is sent. + + This is called by + :meth:`RequestContext.pop() `, + which may be delayed during testing to maintain access to + resources. + + :param exc: An unhandled exception raised while dispatching the + request. Detected from the current exception information if + not passed. Passed to each teardown function. + + .. versionchanged:: 0.9 + Added the ``exc`` argument. + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for name in chain(request.blueprints, (None,)): + if name in self.teardown_request_funcs: + for func in reversed(self.teardown_request_funcs[name]): + self.ensure_sync(func)(exc) + + request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) + + def do_teardown_appcontext( + self, + exc: BaseException | None = _sentinel, # type: ignore[assignment] + ) -> None: + """Called right before the application context is popped. + + When handling a request, the application context is popped + after the request context. See :meth:`do_teardown_request`. + + This calls all functions decorated with + :meth:`teardown_appcontext`. Then the + :data:`appcontext_tearing_down` signal is sent. + + This is called by + :meth:`AppContext.pop() `. + + .. versionadded:: 0.9 + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for func in reversed(self.teardown_appcontext_funcs): + self.ensure_sync(func)(exc) + + appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) + + def app_context(self) -> AppContext: + """Create an :class:`~flask.ctx.AppContext`. Use as a ``with`` + block to push the context, which will make :data:`current_app` + point at this application. + + An application context is automatically pushed by + :meth:`RequestContext.push() ` + when handling a request, and when running a CLI command. Use + this to manually create a context outside of these situations. + + :: + + with app.app_context(): + init_db() + + See :doc:`/appcontext`. + + .. versionadded:: 0.9 + """ + return AppContext(self) + + def request_context(self, environ: WSGIEnvironment) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` representing a + WSGI environment. Use a ``with`` block to push the context, + which will make :data:`request` point at this request. + + See :doc:`/reqcontext`. + + Typically you should not call this from your own code. A request + context is automatically pushed by the :meth:`wsgi_app` when + handling a request. Use :meth:`test_request_context` to create + an environment and context instead of this method. + + :param environ: a WSGI environment + """ + return RequestContext(self, environ) + + def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` for a WSGI + environment created from the given values. This is mostly useful + during testing, where you may want to run a function that uses + request data without dispatching a full request. + + See :doc:`/reqcontext`. + + Use a ``with`` block to push the context, which will make + :data:`request` point at the request for the created + environment. :: + + with app.test_request_context(...): + generate_report() + + When using the shell, it may be easier to push and pop the + context manually to avoid indentation. :: + + ctx = app.test_request_context(...) + ctx.push() + ... + ctx.pop() + + Takes the same arguments as Werkzeug's + :class:`~werkzeug.test.EnvironBuilder`, with some defaults from + the application. See the linked Werkzeug docs for most of the + available arguments. Flask-specific behavior is listed here. + + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to + :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param data: The request body, either as a string or a dict of + form keys and values. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + from .testing import EnvironBuilder + + builder = EnvironBuilder(self, *args, **kwargs) + + try: + return self.request_context(builder.get_environ()) + finally: + builder.close() + + def wsgi_app( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> cabc.Iterable[bytes]: + """The actual WSGI application. This is not implemented in + :meth:`__call__` so that middlewares can be applied without + losing a reference to the app object. Instead of doing this:: + + app = MyMiddleware(app) + + It's a better idea to do this instead:: + + app.wsgi_app = MyMiddleware(app.wsgi_app) + + Then you still have the original application object around and + can continue to call methods on it. + + .. versionchanged:: 0.7 + Teardown events for the request and app contexts are called + even if an unhandled error occurs. Other events may not be + called depending on when an error occurs during dispatch. + See :ref:`callbacks-and-errors`. + + :param environ: A WSGI environment. + :param start_response: A callable accepting a status code, + a list of headers, and an optional exception context to + start the response. + """ + ctx = self.request_context(environ) + error: BaseException | None = None + try: + try: + ctx.push() + response = self.full_dispatch_request() + except Exception as e: + error = e + response = self.handle_exception(e) + except: # noqa: B001 + error = sys.exc_info()[1] + raise + return response(environ, start_response) + finally: + if "werkzeug.debug.preserve_context" in environ: + environ["werkzeug.debug.preserve_context"](_cv_app.get()) + environ["werkzeug.debug.preserve_context"](_cv_request.get()) + + if error is not None and self.should_ignore_error(error): + error = None + + ctx.pop(error) + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> cabc.Iterable[bytes]: + """The WSGI server calls the Flask application object as the + WSGI application. This calls :meth:`wsgi_app`, which can be + wrapped to apply middleware. + """ + return self.wsgi_app(environ, start_response) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/blueprints.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/blueprints.py new file mode 100644 index 0000000..b6d4e43 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/blueprints.py @@ -0,0 +1,128 @@ +from __future__ import annotations + +import os +import typing as t +from datetime import timedelta + +from .cli import AppGroup +from .globals import current_app +from .helpers import send_from_directory +from .sansio.blueprints import Blueprint as SansioBlueprint +from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa +from .sansio.scaffold import _sentinel + +if t.TYPE_CHECKING: # pragma: no cover + from .wrappers import Response + + +class Blueprint(SansioBlueprint): + def __init__( + self, + name: str, + import_name: str, + static_folder: str | os.PathLike[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | None = None, + url_prefix: str | None = None, + subdomain: str | None = None, + url_defaults: dict[str, t.Any] | None = None, + root_path: str | None = None, + cli_group: str | None = _sentinel, # type: ignore + ) -> None: + super().__init__( + name, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_group, + ) + + #: The Click command group for registering CLI commands for this + #: object. The commands are available from the ``flask`` command + #: once the application has been discovered and blueprints have + #: been registered. + self.cli = AppGroup() + + # Set the name of the Click group in case someone wants to add + # the app's commands to another CLI tool. + self.cli.name = self.name + + def get_send_file_max_age(self, filename: str | None) -> int | None: + """Used by :func:`send_file` to determine the ``max_age`` cache + value for a given file path if it wasn't passed. + + By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from + the configuration of :data:`~flask.current_app`. This defaults + to ``None``, which tells the browser to use conditional requests + instead of a timed cache, which is usually preferable. + + Note this is a duplicate of the same method in the Flask + class. + + .. versionchanged:: 2.0 + The default configuration is ``None`` instead of 12 hours. + + .. versionadded:: 0.9 + """ + value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"] + + if value is None: + return None + + if isinstance(value, timedelta): + return int(value.total_seconds()) + + return value # type: ignore[no-any-return] + + def send_static_file(self, filename: str) -> Response: + """The view function used to serve files from + :attr:`static_folder`. A route is automatically registered for + this view at :attr:`static_url_path` if :attr:`static_folder` is + set. + + Note this is a duplicate of the same method in the Flask + class. + + .. versionadded:: 0.5 + + """ + if not self.has_static_folder: + raise RuntimeError("'static_folder' must be set to serve static_files.") + + # send_file only knows to call get_send_file_max_age on the app, + # call it here so it works for blueprints too. + max_age = self.get_send_file_max_age(filename) + return send_from_directory( + t.cast(str, self.static_folder), filename, max_age=max_age + ) + + def open_resource( + self, resource: str, mode: str = "rb", encoding: str | None = "utf-8" + ) -> t.IO[t.AnyStr]: + """Open a resource file relative to :attr:`root_path` for reading. The + blueprint-relative equivalent of the app's :meth:`~.Flask.open_resource` + method. + + :param resource: Path to the resource relative to :attr:`root_path`. + :param mode: Open the file in this mode. Only reading is supported, + valid values are ``"r"`` (or ``"rt"``) and ``"rb"``. + :param encoding: Open the file with this encoding when opening in text + mode. This is ignored when opening in binary mode. + + .. versionchanged:: 3.1 + Added the ``encoding`` parameter. + """ + if mode not in {"r", "rt", "rb"}: + raise ValueError("Resources can only be opened for reading.") + + path = os.path.join(self.root_path, resource) + + if mode == "rb": + return open(path, mode) # pyright: ignore + + return open(path, mode, encoding=encoding) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/cli.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/cli.py new file mode 100644 index 0000000..ed11f25 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/cli.py @@ -0,0 +1,1135 @@ +from __future__ import annotations + +import ast +import collections.abc as cabc +import importlib.metadata +import inspect +import os +import platform +import re +import sys +import traceback +import typing as t +from functools import update_wrapper +from operator import itemgetter +from types import ModuleType + +import click +from click.core import ParameterSource +from werkzeug import run_simple +from werkzeug.serving import is_running_from_reloader +from werkzeug.utils import import_string + +from .globals import current_app +from .helpers import get_debug_flag +from .helpers import get_load_dotenv + +if t.TYPE_CHECKING: + import ssl + + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + from .app import Flask + + +class NoAppException(click.UsageError): + """Raised if an application cannot be found or loaded.""" + + +def find_best_app(module: ModuleType) -> Flask: + """Given a module instance this tries to find the best possible + application in the module or raises an exception. + """ + from . import Flask + + # Search for the most common names first. + for attr_name in ("app", "application"): + app = getattr(module, attr_name, None) + + if isinstance(app, Flask): + return app + + # Otherwise find the only object that is a Flask instance. + matches = [v for v in module.__dict__.values() if isinstance(v, Flask)] + + if len(matches) == 1: + return matches[0] + elif len(matches) > 1: + raise NoAppException( + "Detected multiple Flask applications in module" + f" '{module.__name__}'. Use '{module.__name__}:name'" + " to specify the correct one." + ) + + # Search for app factory functions. + for attr_name in ("create_app", "make_app"): + app_factory = getattr(module, attr_name, None) + + if inspect.isfunction(app_factory): + try: + app = app_factory() + + if isinstance(app, Flask): + return app + except TypeError as e: + if not _called_with_wrong_args(app_factory): + raise + + raise NoAppException( + f"Detected factory '{attr_name}' in module '{module.__name__}'," + " but could not call it without arguments. Use" + f" '{module.__name__}:{attr_name}(args)'" + " to specify arguments." + ) from e + + raise NoAppException( + "Failed to find Flask application or factory in module" + f" '{module.__name__}'. Use '{module.__name__}:name'" + " to specify one." + ) + + +def _called_with_wrong_args(f: t.Callable[..., Flask]) -> bool: + """Check whether calling a function raised a ``TypeError`` because + the call failed or because something in the factory raised the + error. + + :param f: The function that was called. + :return: ``True`` if the call failed. + """ + tb = sys.exc_info()[2] + + try: + while tb is not None: + if tb.tb_frame.f_code is f.__code__: + # In the function, it was called successfully. + return False + + tb = tb.tb_next + + # Didn't reach the function. + return True + finally: + # Delete tb to break a circular reference. + # https://docs.python.org/2/library/sys.html#sys.exc_info + del tb + + +def find_app_by_string(module: ModuleType, app_name: str) -> Flask: + """Check if the given string is a variable name or a function. Call + a function to get the app instance, or return the variable directly. + """ + from . import Flask + + # Parse app_name as a single expression to determine if it's a valid + # attribute name or function call. + try: + expr = ast.parse(app_name.strip(), mode="eval").body + except SyntaxError: + raise NoAppException( + f"Failed to parse {app_name!r} as an attribute name or function call." + ) from None + + if isinstance(expr, ast.Name): + name = expr.id + args = [] + kwargs = {} + elif isinstance(expr, ast.Call): + # Ensure the function name is an attribute name only. + if not isinstance(expr.func, ast.Name): + raise NoAppException( + f"Function reference must be a simple name: {app_name!r}." + ) + + name = expr.func.id + + # Parse the positional and keyword arguments as literals. + try: + args = [ast.literal_eval(arg) for arg in expr.args] + kwargs = { + kw.arg: ast.literal_eval(kw.value) + for kw in expr.keywords + if kw.arg is not None + } + except ValueError: + # literal_eval gives cryptic error messages, show a generic + # message with the full expression instead. + raise NoAppException( + f"Failed to parse arguments as literal values: {app_name!r}." + ) from None + else: + raise NoAppException( + f"Failed to parse {app_name!r} as an attribute name or function call." + ) + + try: + attr = getattr(module, name) + except AttributeError as e: + raise NoAppException( + f"Failed to find attribute {name!r} in {module.__name__!r}." + ) from e + + # If the attribute is a function, call it with any args and kwargs + # to get the real application. + if inspect.isfunction(attr): + try: + app = attr(*args, **kwargs) + except TypeError as e: + if not _called_with_wrong_args(attr): + raise + + raise NoAppException( + f"The factory {app_name!r} in module" + f" {module.__name__!r} could not be called with the" + " specified arguments." + ) from e + else: + app = attr + + if isinstance(app, Flask): + return app + + raise NoAppException( + "A valid Flask application was not obtained from" + f" '{module.__name__}:{app_name}'." + ) + + +def prepare_import(path: str) -> str: + """Given a filename this will try to calculate the python path, add it + to the search path and return the actual module name that is expected. + """ + path = os.path.realpath(path) + + fname, ext = os.path.splitext(path) + if ext == ".py": + path = fname + + if os.path.basename(path) == "__init__": + path = os.path.dirname(path) + + module_name = [] + + # move up until outside package structure (no __init__.py) + while True: + path, name = os.path.split(path) + module_name.append(name) + + if not os.path.exists(os.path.join(path, "__init__.py")): + break + + if sys.path[0] != path: + sys.path.insert(0, path) + + return ".".join(module_name[::-1]) + + +@t.overload +def locate_app( + module_name: str, app_name: str | None, raise_if_not_found: t.Literal[True] = True +) -> Flask: ... + + +@t.overload +def locate_app( + module_name: str, app_name: str | None, raise_if_not_found: t.Literal[False] = ... +) -> Flask | None: ... + + +def locate_app( + module_name: str, app_name: str | None, raise_if_not_found: bool = True +) -> Flask | None: + try: + __import__(module_name) + except ImportError: + # Reraise the ImportError if it occurred within the imported module. + # Determine this by checking whether the trace has a depth > 1. + if sys.exc_info()[2].tb_next: # type: ignore[union-attr] + raise NoAppException( + f"While importing {module_name!r}, an ImportError was" + f" raised:\n\n{traceback.format_exc()}" + ) from None + elif raise_if_not_found: + raise NoAppException(f"Could not import {module_name!r}.") from None + else: + return None + + module = sys.modules[module_name] + + if app_name is None: + return find_best_app(module) + else: + return find_app_by_string(module, app_name) + + +def get_version(ctx: click.Context, param: click.Parameter, value: t.Any) -> None: + if not value or ctx.resilient_parsing: + return + + flask_version = importlib.metadata.version("flask") + werkzeug_version = importlib.metadata.version("werkzeug") + + click.echo( + f"Python {platform.python_version()}\n" + f"Flask {flask_version}\n" + f"Werkzeug {werkzeug_version}", + color=ctx.color, + ) + ctx.exit() + + +version_option = click.Option( + ["--version"], + help="Show the Flask version.", + expose_value=False, + callback=get_version, + is_flag=True, + is_eager=True, +) + + +class ScriptInfo: + """Helper object to deal with Flask applications. This is usually not + necessary to interface with as it's used internally in the dispatching + to click. In future versions of Flask this object will most likely play + a bigger role. Typically it's created automatically by the + :class:`FlaskGroup` but you can also manually create it and pass it + onwards as click object. + + .. versionchanged:: 3.1 + Added the ``load_dotenv_defaults`` parameter and attribute. + """ + + def __init__( + self, + app_import_path: str | None = None, + create_app: t.Callable[..., Flask] | None = None, + set_debug_flag: bool = True, + load_dotenv_defaults: bool = True, + ) -> None: + #: Optionally the import path for the Flask application. + self.app_import_path = app_import_path + #: Optionally a function that is passed the script info to create + #: the instance of the application. + self.create_app = create_app + #: A dictionary with arbitrary data that can be associated with + #: this script info. + self.data: dict[t.Any, t.Any] = {} + self.set_debug_flag = set_debug_flag + + self.load_dotenv_defaults = get_load_dotenv(load_dotenv_defaults) + """Whether default ``.flaskenv`` and ``.env`` files should be loaded. + + ``ScriptInfo`` doesn't load anything, this is for reference when doing + the load elsewhere during processing. + + .. versionadded:: 3.1 + """ + + self._loaded_app: Flask | None = None + + def load_app(self) -> Flask: + """Loads the Flask app (if not yet loaded) and returns it. Calling + this multiple times will just result in the already loaded app to + be returned. + """ + if self._loaded_app is not None: + return self._loaded_app + app: Flask | None = None + if self.create_app is not None: + app = self.create_app() + else: + if self.app_import_path: + path, name = ( + re.split(r":(?![\\/])", self.app_import_path, maxsplit=1) + [None] + )[:2] + import_name = prepare_import(path) + app = locate_app(import_name, name) + else: + for path in ("wsgi.py", "app.py"): + import_name = prepare_import(path) + app = locate_app(import_name, None, raise_if_not_found=False) + + if app is not None: + break + + if app is None: + raise NoAppException( + "Could not locate a Flask application. Use the" + " 'flask --app' option, 'FLASK_APP' environment" + " variable, or a 'wsgi.py' or 'app.py' file in the" + " current directory." + ) + + if self.set_debug_flag: + # Update the app's debug flag through the descriptor so that + # other values repopulate as well. + app.debug = get_debug_flag() + + self._loaded_app = app + return app + + +pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True) + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def with_appcontext(f: F) -> F: + """Wraps a callback so that it's guaranteed to be executed with the + script's application context. + + Custom commands (and their options) registered under ``app.cli`` or + ``blueprint.cli`` will always have an app context available, this + decorator is not required in that case. + + .. versionchanged:: 2.2 + The app context is active for subcommands as well as the + decorated callback. The app context is always available to + ``app.cli`` command and parameter callbacks. + """ + + @click.pass_context + def decorator(ctx: click.Context, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + if not current_app: + app = ctx.ensure_object(ScriptInfo).load_app() + ctx.with_resource(app.app_context()) + + return ctx.invoke(f, *args, **kwargs) + + return update_wrapper(decorator, f) # type: ignore[return-value] + + +class AppGroup(click.Group): + """This works similar to a regular click :class:`~click.Group` but it + changes the behavior of the :meth:`command` decorator so that it + automatically wraps the functions in :func:`with_appcontext`. + + Not to be confused with :class:`FlaskGroup`. + """ + + def command( # type: ignore[override] + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], click.Command]: + """This works exactly like the method of the same name on a regular + :class:`click.Group` but it wraps callbacks in :func:`with_appcontext` + unless it's disabled by passing ``with_appcontext=False``. + """ + wrap_for_ctx = kwargs.pop("with_appcontext", True) + + def decorator(f: t.Callable[..., t.Any]) -> click.Command: + if wrap_for_ctx: + f = with_appcontext(f) + return super(AppGroup, self).command(*args, **kwargs)(f) # type: ignore[no-any-return] + + return decorator + + def group( # type: ignore[override] + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], click.Group]: + """This works exactly like the method of the same name on a regular + :class:`click.Group` but it defaults the group class to + :class:`AppGroup`. + """ + kwargs.setdefault("cls", AppGroup) + return super().group(*args, **kwargs) # type: ignore[no-any-return] + + +def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None: + if value is None: + return None + + info = ctx.ensure_object(ScriptInfo) + info.app_import_path = value + return value + + +# This option is eager so the app will be available if --help is given. +# --help is also eager, so --app must be before it in the param list. +# no_args_is_help bypasses eager processing, so this option must be +# processed manually in that case to ensure FLASK_APP gets picked up. +_app_option = click.Option( + ["-A", "--app"], + metavar="IMPORT", + help=( + "The Flask application or factory function to load, in the form 'module:name'." + " Module can be a dotted import or file path. Name is not required if it is" + " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to" + " pass arguments." + ), + is_eager=True, + expose_value=False, + callback=_set_app, +) + + +def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None: + # If the flag isn't provided, it will default to False. Don't use + # that, let debug be set by env in that case. + source = ctx.get_parameter_source(param.name) # type: ignore[arg-type] + + if source is not None and source in ( + ParameterSource.DEFAULT, + ParameterSource.DEFAULT_MAP, + ): + return None + + # Set with env var instead of ScriptInfo.load so that it can be + # accessed early during a factory function. + os.environ["FLASK_DEBUG"] = "1" if value else "0" + return value + + +_debug_option = click.Option( + ["--debug/--no-debug"], + help="Set debug mode.", + expose_value=False, + callback=_set_debug, +) + + +def _env_file_callback( + ctx: click.Context, param: click.Option, value: str | None +) -> str | None: + try: + import dotenv # noqa: F401 + except ImportError: + # Only show an error if a value was passed, otherwise we still want to + # call load_dotenv and show a message without exiting. + if value is not None: + raise click.BadParameter( + "python-dotenv must be installed to load an env file.", + ctx=ctx, + param=param, + ) from None + + # Load if a value was passed, or we want to load default files, or both. + if value is not None or ctx.obj.load_dotenv_defaults: + load_dotenv(value, load_defaults=ctx.obj.load_dotenv_defaults) + + return value + + +# This option is eager so env vars are loaded as early as possible to be +# used by other options. +_env_file_option = click.Option( + ["-e", "--env-file"], + type=click.Path(exists=True, dir_okay=False), + help=( + "Load environment variables from this file, taking precedence over" + " those set by '.env' and '.flaskenv'. Variables set directly in the" + " environment take highest precedence. python-dotenv must be installed." + ), + is_eager=True, + expose_value=False, + callback=_env_file_callback, +) + + +class FlaskGroup(AppGroup): + """Special subclass of the :class:`AppGroup` group that supports + loading more commands from the configured Flask app. Normally a + developer does not have to interface with this class but there are + some very advanced use cases for which it makes sense to create an + instance of this. see :ref:`custom-scripts`. + + :param add_default_commands: if this is True then the default run and + shell commands will be added. + :param add_version_option: adds the ``--version`` option. + :param create_app: an optional callback that is passed the script info and + returns the loaded app. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param set_debug_flag: Set the app's debug flag. + + .. versionchanged:: 3.1 + ``-e path`` takes precedence over default ``.env`` and ``.flaskenv`` files. + + .. versionchanged:: 2.2 + Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options. + + .. versionchanged:: 2.2 + An app context is pushed when running ``app.cli`` commands, so + ``@with_appcontext`` is no longer required for those commands. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment variables + from :file:`.env` and :file:`.flaskenv` files. + """ + + def __init__( + self, + add_default_commands: bool = True, + create_app: t.Callable[..., Flask] | None = None, + add_version_option: bool = True, + load_dotenv: bool = True, + set_debug_flag: bool = True, + **extra: t.Any, + ) -> None: + params: list[click.Parameter] = list(extra.pop("params", None) or ()) + # Processing is done with option callbacks instead of a group + # callback. This allows users to make a custom group callback + # without losing the behavior. --env-file must come first so + # that it is eagerly evaluated before --app. + params.extend((_env_file_option, _app_option, _debug_option)) + + if add_version_option: + params.append(version_option) + + if "context_settings" not in extra: + extra["context_settings"] = {} + + extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK") + + super().__init__(params=params, **extra) + + self.create_app = create_app + self.load_dotenv = load_dotenv + self.set_debug_flag = set_debug_flag + + if add_default_commands: + self.add_command(run_command) + self.add_command(shell_command) + self.add_command(routes_command) + + self._loaded_plugin_commands = False + + def _load_plugin_commands(self) -> None: + if self._loaded_plugin_commands: + return + + if sys.version_info >= (3, 10): + from importlib import metadata + else: + # Use a backport on Python < 3.10. We technically have + # importlib.metadata on 3.8+, but the API changed in 3.10, + # so use the backport for consistency. + import importlib_metadata as metadata # pyright: ignore + + for ep in metadata.entry_points(group="flask.commands"): + self.add_command(ep.load(), ep.name) + + self._loaded_plugin_commands = True + + def get_command(self, ctx: click.Context, name: str) -> click.Command | None: + self._load_plugin_commands() + # Look up built-in and plugin commands, which should be + # available even if the app fails to load. + rv = super().get_command(ctx, name) + + if rv is not None: + return rv + + info = ctx.ensure_object(ScriptInfo) + + # Look up commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + app = info.load_app() + except NoAppException as e: + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + return None + + # Push an app context for the loaded app unless it is already + # active somehow. This makes the context available to parameter + # and command callbacks without needing @with_appcontext. + if not current_app or current_app._get_current_object() is not app: # type: ignore[attr-defined] + ctx.with_resource(app.app_context()) + + return app.cli.get_command(ctx, name) + + def list_commands(self, ctx: click.Context) -> list[str]: + self._load_plugin_commands() + # Start with the built-in and plugin commands. + rv = set(super().list_commands(ctx)) + info = ctx.ensure_object(ScriptInfo) + + # Add commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + rv.update(info.load_app().cli.list_commands(ctx)) + except NoAppException as e: + # When an app couldn't be loaded, show the error message + # without the traceback. + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + except Exception: + # When any other errors occurred during loading, show the + # full traceback. + click.secho(f"{traceback.format_exc()}\n", err=True, fg="red") + + return sorted(rv) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: click.Context | None = None, + **extra: t.Any, + ) -> click.Context: + # Set a flag to tell app.run to become a no-op. If app.run was + # not in a __name__ == __main__ guard, it would start the server + # when importing, blocking whatever command is being called. + os.environ["FLASK_RUN_FROM_CLI"] = "true" + + if "obj" not in extra and "obj" not in self.context_settings: + extra["obj"] = ScriptInfo( + create_app=self.create_app, + set_debug_flag=self.set_debug_flag, + load_dotenv_defaults=self.load_dotenv, + ) + + return super().make_context(info_name, args, parent=parent, **extra) + + def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]: + if (not args and self.no_args_is_help) or ( + len(args) == 1 and args[0] in self.get_help_option_names(ctx) + ): + # Attempt to load --env-file and --app early in case they + # were given as env vars. Otherwise no_args_is_help will not + # see commands from app.cli. + _env_file_option.handle_parse_result(ctx, {}, []) + _app_option.handle_parse_result(ctx, {}, []) + + return super().parse_args(ctx, args) + + +def _path_is_ancestor(path: str, other: str) -> bool: + """Take ``other`` and remove the length of ``path`` from it. Then join it + to ``path``. If it is the original value, ``path`` is an ancestor of + ``other``.""" + return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other + + +def load_dotenv( + path: str | os.PathLike[str] | None = None, load_defaults: bool = True +) -> bool: + """Load "dotenv" files to set environment variables. A given path takes + precedence over ``.env``, which takes precedence over ``.flaskenv``. After + loading and combining these files, values are only set if the key is not + already set in ``os.environ``. + + This is a no-op if `python-dotenv`_ is not installed. + + .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme + + :param path: Load the file at this location. + :param load_defaults: Search for and load the default ``.flaskenv`` and + ``.env`` files. + :return: ``True`` if at least one env var was loaded. + + .. versionchanged:: 3.1 + Added the ``load_defaults`` parameter. A given path takes precedence + over default files. + + .. versionchanged:: 2.0 + The current directory is not changed to the location of the + loaded file. + + .. versionchanged:: 2.0 + When loading the env files, set the default encoding to UTF-8. + + .. versionchanged:: 1.1.0 + Returns ``False`` when python-dotenv is not installed, or when + the given path isn't a file. + + .. versionadded:: 1.0 + """ + try: + import dotenv + except ImportError: + if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): + click.secho( + " * Tip: There are .env files present. Install python-dotenv" + " to use them.", + fg="yellow", + err=True, + ) + + return False + + data: dict[str, str | None] = {} + + if load_defaults: + for default_name in (".flaskenv", ".env"): + if not (default_path := dotenv.find_dotenv(default_name, usecwd=True)): + continue + + data |= dotenv.dotenv_values(default_path, encoding="utf-8") + + if path is not None and os.path.isfile(path): + data |= dotenv.dotenv_values(path, encoding="utf-8") + + for key, value in data.items(): + if key in os.environ or value is None: + continue + + os.environ[key] = value + + return bool(data) # True if at least one env var was loaded. + + +def show_server_banner(debug: bool, app_import_path: str | None) -> None: + """Show extra startup messages the first time the server is run, + ignoring the reloader. + """ + if is_running_from_reloader(): + return + + if app_import_path is not None: + click.echo(f" * Serving Flask app '{app_import_path}'") + + if debug is not None: + click.echo(f" * Debug mode: {'on' if debug else 'off'}") + + +class CertParamType(click.ParamType): + """Click option type for the ``--cert`` option. Allows either an + existing file, the string ``'adhoc'``, or an import for a + :class:`~ssl.SSLContext` object. + """ + + name = "path" + + def __init__(self) -> None: + self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) + + def convert( + self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None + ) -> t.Any: + try: + import ssl + except ImportError: + raise click.BadParameter( + 'Using "--cert" requires Python to be compiled with SSL support.', + ctx, + param, + ) from None + + try: + return self.path_type(value, param, ctx) + except click.BadParameter: + value = click.STRING(value, param, ctx).lower() + + if value == "adhoc": + try: + import cryptography # noqa: F401 + except ImportError: + raise click.BadParameter( + "Using ad-hoc certificates requires the cryptography library.", + ctx, + param, + ) from None + + return value + + obj = import_string(value, silent=True) + + if isinstance(obj, ssl.SSLContext): + return obj + + raise + + +def _validate_key(ctx: click.Context, param: click.Parameter, value: t.Any) -> t.Any: + """The ``--key`` option must be specified when ``--cert`` is a file. + Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed. + """ + cert = ctx.params.get("cert") + is_adhoc = cert == "adhoc" + + try: + import ssl + except ImportError: + is_context = False + else: + is_context = isinstance(cert, ssl.SSLContext) + + if value is not None: + if is_adhoc: + raise click.BadParameter( + 'When "--cert" is "adhoc", "--key" is not used.', ctx, param + ) + + if is_context: + raise click.BadParameter( + 'When "--cert" is an SSLContext object, "--key" is not used.', + ctx, + param, + ) + + if not cert: + raise click.BadParameter('"--cert" must also be specified.', ctx, param) + + ctx.params["cert"] = cert, value + + else: + if cert and not (is_adhoc or is_context): + raise click.BadParameter('Required when using "--cert".', ctx, param) + + return value + + +class SeparatedPathType(click.Path): + """Click option type that accepts a list of values separated by the + OS's path separator (``:``, ``;`` on Windows). Each value is + validated as a :class:`click.Path` type. + """ + + def convert( + self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None + ) -> t.Any: + items = self.split_envvar_value(value) + # can't call no-arg super() inside list comprehension until Python 3.12 + super_convert = super().convert + return [super_convert(item, param, ctx) for item in items] + + +@click.command("run", short_help="Run a development server.") +@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.") +@click.option("--port", "-p", default=5000, help="The port to bind to.") +@click.option( + "--cert", + type=CertParamType(), + help="Specify a certificate file to use HTTPS.", + is_eager=True, +) +@click.option( + "--key", + type=click.Path(exists=True, dir_okay=False, resolve_path=True), + callback=_validate_key, + expose_value=False, + help="The key file to use when specifying a certificate.", +) +@click.option( + "--reload/--no-reload", + default=None, + help="Enable or disable the reloader. By default the reloader " + "is active if debug is enabled.", +) +@click.option( + "--debugger/--no-debugger", + default=None, + help="Enable or disable the debugger. By default the debugger " + "is active if debug is enabled.", +) +@click.option( + "--with-threads/--without-threads", + default=True, + help="Enable or disable multithreading.", +) +@click.option( + "--extra-files", + default=None, + type=SeparatedPathType(), + help=( + "Extra files that trigger a reload on change. Multiple paths" + f" are separated by {os.path.pathsep!r}." + ), +) +@click.option( + "--exclude-patterns", + default=None, + type=SeparatedPathType(), + help=( + "Files matching these fnmatch patterns will not trigger a reload" + " on change. Multiple patterns are separated by" + f" {os.path.pathsep!r}." + ), +) +@pass_script_info +def run_command( + info: ScriptInfo, + host: str, + port: int, + reload: bool, + debugger: bool, + with_threads: bool, + cert: ssl.SSLContext | tuple[str, str | None] | t.Literal["adhoc"] | None, + extra_files: list[str] | None, + exclude_patterns: list[str] | None, +) -> None: + """Run a local development server. + + This server is for development purposes only. It does not provide + the stability, security, or performance of production WSGI servers. + + The reloader and debugger are enabled by default with the '--debug' + option. + """ + try: + app: WSGIApplication = info.load_app() # pyright: ignore + except Exception as e: + if is_running_from_reloader(): + # When reloading, print out the error immediately, but raise + # it later so the debugger or server can handle it. + traceback.print_exc() + err = e + + def app( + environ: WSGIEnvironment, start_response: StartResponse + ) -> cabc.Iterable[bytes]: + raise err from None + + else: + # When not reloading, raise the error immediately so the + # command fails. + raise e from None + + debug = get_debug_flag() + + if reload is None: + reload = debug + + if debugger is None: + debugger = debug + + show_server_banner(debug, info.app_import_path) + + run_simple( + host, + port, + app, + use_reloader=reload, + use_debugger=debugger, + threaded=with_threads, + ssl_context=cert, + extra_files=extra_files, + exclude_patterns=exclude_patterns, + ) + + +run_command.params.insert(0, _debug_option) + + +@click.command("shell", short_help="Run a shell in the app context.") +@with_appcontext +def shell_command() -> None: + """Run an interactive Python shell in the context of a given + Flask application. The application will populate the default + namespace of this shell according to its configuration. + + This is useful for executing small snippets of management code + without having to manually configure the application. + """ + import code + + banner = ( + f"Python {sys.version} on {sys.platform}\n" + f"App: {current_app.import_name}\n" + f"Instance: {current_app.instance_path}" + ) + ctx: dict[str, t.Any] = {} + + # Support the regular Python interpreter startup script if someone + # is using it. + startup = os.environ.get("PYTHONSTARTUP") + if startup and os.path.isfile(startup): + with open(startup) as f: + eval(compile(f.read(), startup, "exec"), ctx) + + ctx.update(current_app.make_shell_context()) + + # Site, customize, or startup script can set a hook to call when + # entering interactive mode. The default one sets up readline with + # tab and history completion. + interactive_hook = getattr(sys, "__interactivehook__", None) + + if interactive_hook is not None: + try: + import readline + from rlcompleter import Completer + except ImportError: + pass + else: + # rlcompleter uses __main__.__dict__ by default, which is + # flask.__main__. Use the shell context instead. + readline.set_completer(Completer(ctx).complete) + + interactive_hook() + + code.interact(banner=banner, local=ctx) + + +@click.command("routes", short_help="Show the routes for the app.") +@click.option( + "--sort", + "-s", + type=click.Choice(("endpoint", "methods", "domain", "rule", "match")), + default="endpoint", + help=( + "Method to sort routes by. 'match' is the order that Flask will match routes" + " when dispatching a request." + ), +) +@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.") +@with_appcontext +def routes_command(sort: str, all_methods: bool) -> None: + """Show all registered routes with endpoints and methods.""" + rules = list(current_app.url_map.iter_rules()) + + if not rules: + click.echo("No routes were registered.") + return + + ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"} + host_matching = current_app.url_map.host_matching + has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules) + rows = [] + + for rule in rules: + row = [ + rule.endpoint, + ", ".join(sorted((rule.methods or set()) - ignored_methods)), + ] + + if has_domain: + row.append((rule.host if host_matching else rule.subdomain) or "") + + row.append(rule.rule) + rows.append(row) + + headers = ["Endpoint", "Methods"] + sorts = ["endpoint", "methods"] + + if has_domain: + headers.append("Host" if host_matching else "Subdomain") + sorts.append("domain") + + headers.append("Rule") + sorts.append("rule") + + try: + rows.sort(key=itemgetter(sorts.index(sort))) + except ValueError: + pass + + rows.insert(0, headers) + widths = [max(len(row[i]) for row in rows) for i in range(len(headers))] + rows.insert(1, ["-" * w for w in widths]) + template = " ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths)) + + for row in rows: + click.echo(template.format(*row)) + + +cli = FlaskGroup( + name="flask", + help="""\ +A general utility script for Flask applications. + +An application to load must be given with the '--app' option, +'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file +in the current directory. +""", +) + + +def main() -> None: + cli.main() + + +if __name__ == "__main__": + main() diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/config.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/config.py new file mode 100644 index 0000000..34ef1a5 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/config.py @@ -0,0 +1,367 @@ +from __future__ import annotations + +import errno +import json +import os +import types +import typing as t + +from werkzeug.utils import import_string + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .sansio.app import App + + +T = t.TypeVar("T") + + +class ConfigAttribute(t.Generic[T]): + """Makes an attribute forward to the config""" + + def __init__( + self, name: str, get_converter: t.Callable[[t.Any], T] | None = None + ) -> None: + self.__name__ = name + self.get_converter = get_converter + + @t.overload + def __get__(self, obj: None, owner: None) -> te.Self: ... + + @t.overload + def __get__(self, obj: App, owner: type[App]) -> T: ... + + def __get__(self, obj: App | None, owner: type[App] | None = None) -> T | te.Self: + if obj is None: + return self + + rv = obj.config[self.__name__] + + if self.get_converter is not None: + rv = self.get_converter(rv) + + return rv # type: ignore[no-any-return] + + def __set__(self, obj: App, value: t.Any) -> None: + obj.config[self.__name__] = value + + +class Config(dict): # type: ignore[type-arg] + """Works exactly like a dict but provides ways to fill it from files + or special dictionaries. There are two common patterns to populate the + config. + + Either you can fill the config from a config file:: + + app.config.from_pyfile('yourconfig.cfg') + + Or alternatively you can define the configuration options in the + module that calls :meth:`from_object` or provide an import path to + a module that should be loaded. It is also possible to tell it to + use the same module and with that provide the configuration values + just before the call:: + + DEBUG = True + SECRET_KEY = 'development key' + app.config.from_object(__name__) + + In both cases (loading from any Python file or loading from modules), + only uppercase keys are added to the config. This makes it possible to use + lowercase values in the config file for temporary values that are not added + to the config or to define the config keys in the same file that implements + the application. + + Probably the most interesting way to load configurations is from an + environment variable pointing to a file:: + + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + + In this case before launching the application you have to set this + environment variable to the file you want to use. On Linux and OS X + use the export statement:: + + export YOURAPPLICATION_SETTINGS='/path/to/config/file' + + On windows use `set` instead. + + :param root_path: path to which files are read relative from. When the + config object is created by the application, this is + the application's :attr:`~flask.Flask.root_path`. + :param defaults: an optional dictionary of default values + """ + + def __init__( + self, + root_path: str | os.PathLike[str], + defaults: dict[str, t.Any] | None = None, + ) -> None: + super().__init__(defaults or {}) + self.root_path = root_path + + def from_envvar(self, variable_name: str, silent: bool = False) -> bool: + """Loads a configuration from an environment variable pointing to + a configuration file. This is basically just a shortcut with nicer + error messages for this line of code:: + + app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) + + :param variable_name: name of the environment variable + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + """ + rv = os.environ.get(variable_name) + if not rv: + if silent: + return False + raise RuntimeError( + f"The environment variable {variable_name!r} is not set" + " and as such configuration could not be loaded. Set" + " this variable and make it point to a configuration" + " file" + ) + return self.from_pyfile(rv, silent=silent) + + def from_prefixed_env( + self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads + ) -> bool: + """Load any environment variables that start with ``FLASK_``, + dropping the prefix from the env key for the config key. Values + are passed through a loading function to attempt to convert them + to more specific types than strings. + + Keys are loaded in :func:`sorted` order. + + The default loading function attempts to parse values as any + valid JSON type, including dicts and lists. + + Specific items in nested dicts can be set by separating the + keys with double underscores (``__``). If an intermediate key + doesn't exist, it will be initialized to an empty dict. + + :param prefix: Load env vars that start with this prefix, + separated with an underscore (``_``). + :param loads: Pass each string value to this function and use + the returned value as the config value. If any error is + raised it is ignored and the value remains a string. The + default is :func:`json.loads`. + + .. versionadded:: 2.1 + """ + prefix = f"{prefix}_" + + for key in sorted(os.environ): + if not key.startswith(prefix): + continue + + value = os.environ[key] + key = key.removeprefix(prefix) + + try: + value = loads(value) + except Exception: + # Keep the value as a string if loading failed. + pass + + if "__" not in key: + # A non-nested key, set directly. + self[key] = value + continue + + # Traverse nested dictionaries with keys separated by "__". + current = self + *parts, tail = key.split("__") + + for part in parts: + # If an intermediate dict does not exist, create it. + if part not in current: + current[part] = {} + + current = current[part] + + current[tail] = value + + return True + + def from_pyfile( + self, filename: str | os.PathLike[str], silent: bool = False + ) -> bool: + """Updates the values in the config from a Python file. This function + behaves as if the file was imported as module with the + :meth:`from_object` function. + + :param filename: the filename of the config. This can either be an + absolute filename or a filename relative to the + root path. + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + + .. versionadded:: 0.7 + `silent` parameter. + """ + filename = os.path.join(self.root_path, filename) + d = types.ModuleType("config") + d.__file__ = filename + try: + with open(filename, mode="rb") as config_file: + exec(compile(config_file.read(), filename, "exec"), d.__dict__) + except OSError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): + return False + e.strerror = f"Unable to load configuration file ({e.strerror})" + raise + self.from_object(d) + return True + + def from_object(self, obj: object | str) -> None: + """Updates the values from the given object. An object can be of one + of the following two types: + + - a string: in this case the object with that name will be imported + - an actual object reference: that object is used directly + + Objects are usually either modules or classes. :meth:`from_object` + loads only the uppercase attributes of the module/class. A ``dict`` + object will not work with :meth:`from_object` because the keys of a + ``dict`` are not attributes of the ``dict`` class. + + Example of module-based configuration:: + + app.config.from_object('yourapplication.default_config') + from yourapplication import default_config + app.config.from_object(default_config) + + Nothing is done to the object before loading. If the object is a + class and has ``@property`` attributes, it needs to be + instantiated before being passed to this method. + + You should not use this function to load the actual configuration but + rather configuration defaults. The actual config should be loaded + with :meth:`from_pyfile` and ideally from a location not within the + package because the package might be installed system wide. + + See :ref:`config-dev-prod` for an example of class-based configuration + using :meth:`from_object`. + + :param obj: an import name or object + """ + if isinstance(obj, str): + obj = import_string(obj) + for key in dir(obj): + if key.isupper(): + self[key] = getattr(obj, key) + + def from_file( + self, + filename: str | os.PathLike[str], + load: t.Callable[[t.IO[t.Any]], t.Mapping[str, t.Any]], + silent: bool = False, + text: bool = True, + ) -> bool: + """Update the values in the config from a file that is loaded + using the ``load`` parameter. The loaded data is passed to the + :meth:`from_mapping` method. + + .. code-block:: python + + import json + app.config.from_file("config.json", load=json.load) + + import tomllib + app.config.from_file("config.toml", load=tomllib.load, text=False) + + :param filename: The path to the data file. This can be an + absolute path or relative to the config root path. + :param load: A callable that takes a file handle and returns a + mapping of loaded data from the file. + :type load: ``Callable[[Reader], Mapping]`` where ``Reader`` + implements a ``read`` method. + :param silent: Ignore the file if it doesn't exist. + :param text: Open the file in text or binary mode. + :return: ``True`` if the file was loaded successfully. + + .. versionchanged:: 2.3 + The ``text`` parameter was added. + + .. versionadded:: 2.0 + """ + filename = os.path.join(self.root_path, filename) + + try: + with open(filename, "r" if text else "rb") as f: + obj = load(f) + except OSError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR): + return False + + e.strerror = f"Unable to load configuration file ({e.strerror})" + raise + + return self.from_mapping(obj) + + def from_mapping( + self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any + ) -> bool: + """Updates the config like :meth:`update` ignoring items with + non-upper keys. + + :return: Always returns ``True``. + + .. versionadded:: 0.11 + """ + mappings: dict[str, t.Any] = {} + if mapping is not None: + mappings.update(mapping) + mappings.update(kwargs) + for key, value in mappings.items(): + if key.isupper(): + self[key] = value + return True + + def get_namespace( + self, namespace: str, lowercase: bool = True, trim_namespace: bool = True + ) -> dict[str, t.Any]: + """Returns a dictionary containing a subset of configuration options + that match the specified namespace/prefix. Example usage:: + + app.config['IMAGE_STORE_TYPE'] = 'fs' + app.config['IMAGE_STORE_PATH'] = '/var/app/images' + app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com' + image_store_config = app.config.get_namespace('IMAGE_STORE_') + + The resulting dictionary `image_store_config` would look like:: + + { + 'type': 'fs', + 'path': '/var/app/images', + 'base_url': 'http://img.website.com' + } + + This is often useful when configuration options map directly to + keyword arguments in functions or class constructors. + + :param namespace: a configuration namespace + :param lowercase: a flag indicating if the keys of the resulting + dictionary should be lowercase + :param trim_namespace: a flag indicating if the keys of the resulting + dictionary should not include the namespace + + .. versionadded:: 0.11 + """ + rv = {} + for k, v in self.items(): + if not k.startswith(namespace): + continue + if trim_namespace: + key = k[len(namespace) :] + else: + key = k + if lowercase: + key = key.lower() + rv[key] = v + return rv + + def __repr__(self) -> str: + return f"<{type(self).__name__} {dict.__repr__(self)}>" diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/ctx.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/ctx.py new file mode 100644 index 0000000..222e818 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/ctx.py @@ -0,0 +1,449 @@ +from __future__ import annotations + +import contextvars +import sys +import typing as t +from functools import update_wrapper +from types import TracebackType + +from werkzeug.exceptions import HTTPException + +from . import typing as ft +from .globals import _cv_app +from .globals import _cv_request +from .signals import appcontext_popped +from .signals import appcontext_pushed + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import WSGIEnvironment + + from .app import Flask + from .sessions import SessionMixin + from .wrappers import Request + + +# a singleton sentinel value for parameter defaults +_sentinel = object() + + +class _AppCtxGlobals: + """A plain object. Used as a namespace for storing data during an + application context. + + Creating an app context automatically creates this object, which is + made available as the :data:`g` proxy. + + .. describe:: 'key' in g + + Check whether an attribute is present. + + .. versionadded:: 0.10 + + .. describe:: iter(g) + + Return an iterator over the attribute names. + + .. versionadded:: 0.10 + """ + + # Define attr methods to let mypy know this is a namespace object + # that has arbitrary attributes. + + def __getattr__(self, name: str) -> t.Any: + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def __setattr__(self, name: str, value: t.Any) -> None: + self.__dict__[name] = value + + def __delattr__(self, name: str) -> None: + try: + del self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def get(self, name: str, default: t.Any | None = None) -> t.Any: + """Get an attribute by name, or a default value. Like + :meth:`dict.get`. + + :param name: Name of attribute to get. + :param default: Value to return if the attribute is not present. + + .. versionadded:: 0.10 + """ + return self.__dict__.get(name, default) + + def pop(self, name: str, default: t.Any = _sentinel) -> t.Any: + """Get and remove an attribute by name. Like :meth:`dict.pop`. + + :param name: Name of attribute to pop. + :param default: Value to return if the attribute is not present, + instead of raising a ``KeyError``. + + .. versionadded:: 0.11 + """ + if default is _sentinel: + return self.__dict__.pop(name) + else: + return self.__dict__.pop(name, default) + + def setdefault(self, name: str, default: t.Any = None) -> t.Any: + """Get the value of an attribute if it is present, otherwise + set and return a default value. Like :meth:`dict.setdefault`. + + :param name: Name of attribute to get. + :param default: Value to set and return if the attribute is not + present. + + .. versionadded:: 0.11 + """ + return self.__dict__.setdefault(name, default) + + def __contains__(self, item: str) -> bool: + return item in self.__dict__ + + def __iter__(self) -> t.Iterator[str]: + return iter(self.__dict__) + + def __repr__(self) -> str: + ctx = _cv_app.get(None) + if ctx is not None: + return f"" + return object.__repr__(self) + + +def after_this_request( + f: ft.AfterRequestCallable[t.Any], +) -> ft.AfterRequestCallable[t.Any]: + """Executes a function after this request. This is useful to modify + response objects. The function is passed the response object and has + to return the same or a new one. + + Example:: + + @app.route('/') + def index(): + @after_this_request + def add_header(response): + response.headers['X-Foo'] = 'Parachute' + return response + return 'Hello World!' + + This is more useful if a function other than the view function wants to + modify a response. For instance think of a decorator that wants to add + some headers without converting the return value into a response object. + + .. versionadded:: 0.9 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'after_this_request' can only be used when a request" + " context is active, such as in a view function." + ) + + ctx._after_request_functions.append(f) + return f + + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def copy_current_request_context(f: F) -> F: + """A helper function that decorates a function to retain the current + request context. This is useful when working with greenlets. The moment + the function is decorated a copy of the request context is created and + then pushed when the function is called. The current session is also + included in the copied request context. + + Example:: + + import gevent + from flask import copy_current_request_context + + @app.route('/') + def index(): + @copy_current_request_context + def do_some_work(): + # do some work here, it can access flask.request or + # flask.session like you would otherwise in the view function. + ... + gevent.spawn(do_some_work) + return 'Regular response' + + .. versionadded:: 0.10 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'copy_current_request_context' can only be used when a" + " request context is active, such as in a view function." + ) + + ctx = ctx.copy() + + def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any: + with ctx: + return ctx.app.ensure_sync(f)(*args, **kwargs) + + return update_wrapper(wrapper, f) # type: ignore[return-value] + + +def has_request_context() -> bool: + """If you have code that wants to test if a request context is there or + not this function can be used. For instance, you may want to take advantage + of request information if the request object is available, but fail + silently if it is unavailable. + + :: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and has_request_context(): + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + Alternatively you can also just test any of the context bound objects + (such as :class:`request` or :class:`g`) for truthness:: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and request: + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + .. versionadded:: 0.7 + """ + return _cv_request.get(None) is not None + + +def has_app_context() -> bool: + """Works like :func:`has_request_context` but for the application + context. You can also just do a boolean check on the + :data:`current_app` object instead. + + .. versionadded:: 0.9 + """ + return _cv_app.get(None) is not None + + +class AppContext: + """The app context contains application-specific information. An app + context is created and pushed at the beginning of each request if + one is not already active. An app context is also pushed when + running CLI commands. + """ + + def __init__(self, app: Flask) -> None: + self.app = app + self.url_adapter = app.create_url_adapter(None) + self.g: _AppCtxGlobals = app.app_ctx_globals_class() + self._cv_tokens: list[contextvars.Token[AppContext]] = [] + + def push(self) -> None: + """Binds the app context to the current context.""" + self._cv_tokens.append(_cv_app.set(self)) + appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync) + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the app context.""" + try: + if len(self._cv_tokens) == 1: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) + finally: + ctx = _cv_app.get() + _cv_app.reset(self._cv_tokens.pop()) + + if ctx is not self: + raise AssertionError( + f"Popped wrong app context. ({ctx!r} instead of {self!r})" + ) + + appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync) + + def __enter__(self) -> AppContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + +class RequestContext: + """The request context contains per-request information. The Flask + app creates and pushes it at the beginning of the request, then pops + it at the end of the request. It will create the URL adapter and + request object for the WSGI environment provided. + + Do not attempt to use this class directly, instead use + :meth:`~flask.Flask.test_request_context` and + :meth:`~flask.Flask.request_context` to create this object. + + When the request context is popped, it will evaluate all the + functions registered on the application for teardown execution + (:meth:`~flask.Flask.teardown_request`). + + The request context is automatically popped at the end of the + request. When using the interactive debugger, the context will be + restored so ``request`` is still accessible. Similarly, the test + client can preserve the context after the request ends. However, + teardown functions may already have closed some resources such as + database connections. + """ + + def __init__( + self, + app: Flask, + environ: WSGIEnvironment, + request: Request | None = None, + session: SessionMixin | None = None, + ) -> None: + self.app = app + if request is None: + request = app.request_class(environ) + request.json_module = app.json + self.request: Request = request + self.url_adapter = None + try: + self.url_adapter = app.create_url_adapter(self.request) + except HTTPException as e: + self.request.routing_exception = e + self.flashes: list[tuple[str, str]] | None = None + self.session: SessionMixin | None = session + # Functions that should be executed after the request on the response + # object. These will be called before the regular "after_request" + # functions. + self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = [] + + self._cv_tokens: list[ + tuple[contextvars.Token[RequestContext], AppContext | None] + ] = [] + + def copy(self) -> RequestContext: + """Creates a copy of this request context with the same request object. + This can be used to move a request context to a different greenlet. + Because the actual request object is the same this cannot be used to + move a request context to a different thread unless access to the + request object is locked. + + .. versionadded:: 0.10 + + .. versionchanged:: 1.1 + The current session object is used instead of reloading the original + data. This prevents `flask.session` pointing to an out-of-date object. + """ + return self.__class__( + self.app, + environ=self.request.environ, + request=self.request, + session=self.session, + ) + + def match_request(self) -> None: + """Can be overridden by a subclass to hook into the matching + of the request. + """ + try: + result = self.url_adapter.match(return_rule=True) # type: ignore + self.request.url_rule, self.request.view_args = result # type: ignore + except HTTPException as e: + self.request.routing_exception = e + + def push(self) -> None: + # Before we push the request context we have to ensure that there + # is an application context. + app_ctx = _cv_app.get(None) + + if app_ctx is None or app_ctx.app is not self.app: + app_ctx = self.app.app_context() + app_ctx.push() + else: + app_ctx = None + + self._cv_tokens.append((_cv_request.set(self), app_ctx)) + + # Open the session at the moment that the request context is available. + # This allows a custom open_session method to use the request context. + # Only open a new session if this is the first time the request was + # pushed, otherwise stream_with_context loses the session. + if self.session is None: + session_interface = self.app.session_interface + self.session = session_interface.open_session(self.app, self.request) + + if self.session is None: + self.session = session_interface.make_null_session(self.app) + + # Match the request URL after loading the session, so that the + # session is available in custom URL converters. + if self.url_adapter is not None: + self.match_request() + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the request context and unbinds it by doing that. This will + also trigger the execution of functions registered by the + :meth:`~flask.Flask.teardown_request` decorator. + + .. versionchanged:: 0.9 + Added the `exc` argument. + """ + clear_request = len(self._cv_tokens) == 1 + + try: + if clear_request: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) + + request_close = getattr(self.request, "close", None) + if request_close is not None: + request_close() + finally: + ctx = _cv_request.get() + token, app_ctx = self._cv_tokens.pop() + _cv_request.reset(token) + + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + if clear_request: + ctx.request.environ["werkzeug.request"] = None + + if app_ctx is not None: + app_ctx.pop(exc) + + if ctx is not self: + raise AssertionError( + f"Popped wrong request context. ({ctx!r} instead of {self!r})" + ) + + def __enter__(self) -> RequestContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + def __repr__(self) -> str: + return ( + f"<{type(self).__name__} {self.request.url!r}" + f" [{self.request.method}] of {self.app.name}>" + ) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/debughelpers.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/debughelpers.py new file mode 100644 index 0000000..2c8c4c4 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/debughelpers.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import typing as t + +from jinja2.loaders import BaseLoader +from werkzeug.routing import RequestRedirect + +from .blueprints import Blueprint +from .globals import request_ctx +from .sansio.app import App + +if t.TYPE_CHECKING: + from .sansio.scaffold import Scaffold + from .wrappers import Request + + +class UnexpectedUnicodeError(AssertionError, UnicodeError): + """Raised in places where we want some better error reporting for + unexpected unicode or binary data. + """ + + +class DebugFilesKeyError(KeyError, AssertionError): + """Raised from request.files during debugging. The idea is that it can + provide a better error message than just a generic KeyError/BadRequest. + """ + + def __init__(self, request: Request, key: str) -> None: + form_matches = request.form.getlist(key) + buf = [ + f"You tried to access the file {key!r} in the request.files" + " dictionary but it does not exist. The mimetype for the" + f" request is {request.mimetype!r} instead of" + " 'multipart/form-data' which means that no file contents" + " were transmitted. To fix this error you should provide" + ' enctype="multipart/form-data" in your form.' + ] + if form_matches: + names = ", ".join(repr(x) for x in form_matches) + buf.append( + "\n\nThe browser instead transmitted some file names. " + f"This was submitted: {names}" + ) + self.msg = "".join(buf) + + def __str__(self) -> str: + return self.msg + + +class FormDataRoutingRedirect(AssertionError): + """This exception is raised in debug mode if a routing redirect + would cause the browser to drop the method or body. This happens + when method is not GET, HEAD or OPTIONS and the status code is not + 307 or 308. + """ + + def __init__(self, request: Request) -> None: + exc = request.routing_exception + assert isinstance(exc, RequestRedirect) + buf = [ + f"A request was sent to '{request.url}', but routing issued" + f" a redirect to the canonical URL '{exc.new_url}'." + ] + + if f"{request.base_url}/" == exc.new_url.partition("?")[0]: + buf.append( + " The URL was defined with a trailing slash. Flask" + " will redirect to the URL with a trailing slash if it" + " was accessed without one." + ) + + buf.append( + " Send requests to the canonical URL, or use 307 or 308 for" + " routing redirects. Otherwise, browsers will drop form" + " data.\n\n" + "This exception is only raised in debug mode." + ) + super().__init__("".join(buf)) + + +def attach_enctype_error_multidict(request: Request) -> None: + """Patch ``request.files.__getitem__`` to raise a descriptive error + about ``enctype=multipart/form-data``. + + :param request: The request to patch. + :meta private: + """ + oldcls = request.files.__class__ + + class newcls(oldcls): # type: ignore[valid-type, misc] + def __getitem__(self, key: str) -> t.Any: + try: + return super().__getitem__(key) + except KeyError as e: + if key not in request.form: + raise + + raise DebugFilesKeyError(request, key).with_traceback( + e.__traceback__ + ) from None + + newcls.__name__ = oldcls.__name__ + newcls.__module__ = oldcls.__module__ + request.files.__class__ = newcls + + +def _dump_loader_info(loader: BaseLoader) -> t.Iterator[str]: + yield f"class: {type(loader).__module__}.{type(loader).__name__}" + for key, value in sorted(loader.__dict__.items()): + if key.startswith("_"): + continue + if isinstance(value, (tuple, list)): + if not all(isinstance(x, str) for x in value): + continue + yield f"{key}:" + for item in value: + yield f" - {item}" + continue + elif not isinstance(value, (str, int, float, bool)): + continue + yield f"{key}: {value!r}" + + +def explain_template_loading_attempts( + app: App, + template: str, + attempts: list[ + tuple[ + BaseLoader, + Scaffold, + tuple[str, str | None, t.Callable[[], bool] | None] | None, + ] + ], +) -> None: + """This should help developers understand what failed""" + info = [f"Locating template {template!r}:"] + total_found = 0 + blueprint = None + if request_ctx and request_ctx.request.blueprint is not None: + blueprint = request_ctx.request.blueprint + + for idx, (loader, srcobj, triple) in enumerate(attempts): + if isinstance(srcobj, App): + src_info = f"application {srcobj.import_name!r}" + elif isinstance(srcobj, Blueprint): + src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})" + else: + src_info = repr(srcobj) + + info.append(f"{idx + 1:5}: trying loader of {src_info}") + + for line in _dump_loader_info(loader): + info.append(f" {line}") + + if triple is None: + detail = "no match" + else: + detail = f"found ({triple[1] or ''!r})" + total_found += 1 + info.append(f" -> {detail}") + + seems_fishy = False + if total_found == 0: + info.append("Error: the template could not be found.") + seems_fishy = True + elif total_found > 1: + info.append("Warning: multiple loaders returned a match for the template.") + seems_fishy = True + + if blueprint is not None and seems_fishy: + info.append( + " The template was looked up from an endpoint that belongs" + f" to the blueprint {blueprint!r}." + ) + info.append(" Maybe you did not place a template in the right folder?") + info.append(" See https://flask.palletsprojects.com/blueprints/#templates") + + app.logger.info("\n".join(info)) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/globals.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/globals.py new file mode 100644 index 0000000..e2c410c --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/globals.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import typing as t +from contextvars import ContextVar + +from werkzeug.local import LocalProxy + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .ctx import _AppCtxGlobals + from .ctx import AppContext + from .ctx import RequestContext + from .sessions import SessionMixin + from .wrappers import Request + + +_no_app_msg = """\ +Working outside of application context. + +This typically means that you attempted to use functionality that needed +the current application. To solve this, set up an application context +with app.app_context(). See the documentation for more information.\ +""" +_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx") +app_ctx: AppContext = LocalProxy( # type: ignore[assignment] + _cv_app, unbound_message=_no_app_msg +) +current_app: Flask = LocalProxy( # type: ignore[assignment] + _cv_app, "app", unbound_message=_no_app_msg +) +g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment] + _cv_app, "g", unbound_message=_no_app_msg +) + +_no_req_msg = """\ +Working outside of request context. + +This typically means that you attempted to use functionality that needed +an active HTTP request. Consult the documentation on testing for +information about how to avoid this problem.\ +""" +_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx") +request_ctx: RequestContext = LocalProxy( # type: ignore[assignment] + _cv_request, unbound_message=_no_req_msg +) +request: Request = LocalProxy( # type: ignore[assignment] + _cv_request, "request", unbound_message=_no_req_msg +) +session: SessionMixin = LocalProxy( # type: ignore[assignment] + _cv_request, "session", unbound_message=_no_req_msg +) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/helpers.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/helpers.py new file mode 100644 index 0000000..5d412c9 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/helpers.py @@ -0,0 +1,641 @@ +from __future__ import annotations + +import importlib.util +import os +import sys +import typing as t +from datetime import datetime +from functools import cache +from functools import update_wrapper + +import werkzeug.utils +from werkzeug.exceptions import abort as _wz_abort +from werkzeug.utils import redirect as _wz_redirect +from werkzeug.wrappers import Response as BaseResponse + +from .globals import _cv_app +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .globals import request_ctx +from .globals import session +from .signals import message_flashed + +if t.TYPE_CHECKING: # pragma: no cover + from .wrappers import Response + + +def get_debug_flag() -> bool: + """Get whether debug mode should be enabled for the app, indicated by the + :envvar:`FLASK_DEBUG` environment variable. The default is ``False``. + """ + val = os.environ.get("FLASK_DEBUG") + return bool(val and val.lower() not in {"0", "false", "no"}) + + +def get_load_dotenv(default: bool = True) -> bool: + """Get whether the user has disabled loading default dotenv files by + setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load + the files. + + :param default: What to return if the env var isn't set. + """ + val = os.environ.get("FLASK_SKIP_DOTENV") + + if not val: + return default + + return val.lower() in ("0", "false", "no") + + +@t.overload +def stream_with_context( + generator_or_function: t.Iterator[t.AnyStr], +) -> t.Iterator[t.AnyStr]: ... + + +@t.overload +def stream_with_context( + generator_or_function: t.Callable[..., t.Iterator[t.AnyStr]], +) -> t.Callable[[t.Iterator[t.AnyStr]], t.Iterator[t.AnyStr]]: ... + + +def stream_with_context( + generator_or_function: t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]], +) -> t.Iterator[t.AnyStr] | t.Callable[[t.Iterator[t.AnyStr]], t.Iterator[t.AnyStr]]: + """Wrap a response generator function so that it runs inside the current + request context. This keeps :data:`request`, :data:`session`, and :data:`g` + available, even though at the point the generator runs the request context + will typically have ended. + + Use it as a decorator on a generator function: + + .. code-block:: python + + from flask import stream_with_context, request, Response + + @app.get("/stream") + def streamed_response(): + @stream_with_context + def generate(): + yield "Hello " + yield request.args["name"] + yield "!" + + return Response(generate()) + + Or use it as a wrapper around a created generator: + + .. code-block:: python + + from flask import stream_with_context, request, Response + + @app.get("/stream") + def streamed_response(): + def generate(): + yield "Hello " + yield request.args["name"] + yield "!" + + return Response(stream_with_context(generate())) + + .. versionadded:: 0.9 + """ + try: + gen = iter(generator_or_function) # type: ignore[arg-type] + except TypeError: + + def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any: + gen = generator_or_function(*args, **kwargs) # type: ignore[operator] + return stream_with_context(gen) + + return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type] + + def generator() -> t.Iterator[t.AnyStr]: + if (req_ctx := _cv_request.get(None)) is None: + raise RuntimeError( + "'stream_with_context' can only be used when a request" + " context is active, such as in a view function." + ) + + app_ctx = _cv_app.get() + # Setup code below will run the generator to this point, so that the + # current contexts are recorded. The contexts must be pushed after, + # otherwise their ContextVar will record the wrong event loop during + # async view functions. + yield None # type: ignore[misc] + + # Push the app context first, so that the request context does not + # automatically create and push a different app context. + with app_ctx, req_ctx: + try: + yield from gen + finally: + # Clean up in case the user wrapped a WSGI iterator. + if hasattr(gen, "close"): + gen.close() + + # Execute the generator to the sentinel value. This ensures the context is + # preserved in the generator's state. Further iteration will push the + # context and yield from the original iterator. + wrapped_g = generator() + next(wrapped_g) + return wrapped_g + + +def make_response(*args: t.Any) -> Response: + """Sometimes it is necessary to set additional headers in a view. Because + views do not have to return response objects but can return a value that + is converted into a response object by Flask itself, it becomes tricky to + add headers to it. This function can be called instead of using a return + and you will get a response object which you can use to attach headers. + + If view looked like this and you want to add a new header:: + + def index(): + return render_template('index.html', foo=42) + + You can now do something like this:: + + def index(): + response = make_response(render_template('index.html', foo=42)) + response.headers['X-Parachutes'] = 'parachutes are cool' + return response + + This function accepts the very same arguments you can return from a + view function. This for example creates a response with a 404 error + code:: + + response = make_response(render_template('not_found.html'), 404) + + The other use case of this function is to force the return value of a + view function into a response which is helpful with view + decorators:: + + response = make_response(view_function()) + response.headers['X-Parachutes'] = 'parachutes are cool' + + Internally this function does the following things: + + - if no arguments are passed, it creates a new response argument + - if one argument is passed, :meth:`flask.Flask.make_response` + is invoked with it. + - if more than one argument is passed, the arguments are passed + to the :meth:`flask.Flask.make_response` function as tuple. + + .. versionadded:: 0.6 + """ + if not args: + return current_app.response_class() + if len(args) == 1: + args = args[0] + return current_app.make_response(args) + + +def url_for( + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, +) -> str: + """Generate a URL to the given endpoint with the given values. + + This requires an active request or application context, and calls + :meth:`current_app.url_for() `. See that method + for full documentation. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it is + external. + :param _external: If given, prefer the URL to be internal (False) or + require it to be external (True). External URLs include the + scheme and domain. When not in an active request, URLs are + external by default. + :param values: Values to use for the variable parts of the URL rule. + Unknown keys are appended as query string arguments, like + ``?a=b&c=d``. + + .. versionchanged:: 2.2 + Calls ``current_app.url_for``, allowing an app to override the + behavior. + + .. versionchanged:: 0.10 + The ``_scheme`` parameter was added. + + .. versionchanged:: 0.9 + The ``_anchor`` and ``_method`` parameters were added. + + .. versionchanged:: 0.9 + Calls ``app.handle_url_build_error`` on build errors. + """ + return current_app.url_for( + endpoint, + _anchor=_anchor, + _method=_method, + _scheme=_scheme, + _external=_external, + **values, + ) + + +def redirect( + location: str, code: int = 302, Response: type[BaseResponse] | None = None +) -> BaseResponse: + """Create a redirect response object. + + If :data:`~flask.current_app` is available, it will use its + :meth:`~flask.Flask.redirect` method, otherwise it will use + :func:`werkzeug.utils.redirect`. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + :param Response: The response class to use. Not used when + ``current_app`` is active, which uses ``app.response_class``. + + .. versionadded:: 2.2 + Calls ``current_app.redirect`` if available instead of always + using Werkzeug's default ``redirect``. + """ + if current_app: + return current_app.redirect(location, code=code) + + return _wz_redirect(location, code=code, Response=Response) + + +def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given + status code. + + If :data:`~flask.current_app` is available, it will call its + :attr:`~flask.Flask.aborter` object, otherwise it will use + :func:`werkzeug.exceptions.abort`. + + :param code: The status code for the exception, which must be + registered in ``app.aborter``. + :param args: Passed to the exception. + :param kwargs: Passed to the exception. + + .. versionadded:: 2.2 + Calls ``current_app.aborter`` if available instead of always + using Werkzeug's default ``abort``. + """ + if current_app: + current_app.aborter(code, *args, **kwargs) + + _wz_abort(code, *args, **kwargs) + + +def get_template_attribute(template_name: str, attribute: str) -> t.Any: + """Loads a macro (or variable) a template exports. This can be used to + invoke a macro from within Python code. If you for example have a + template named :file:`_cider.html` with the following contents: + + .. sourcecode:: html+jinja + + {% macro hello(name) %}Hello {{ name }}!{% endmacro %} + + You can access this from Python code like this:: + + hello = get_template_attribute('_cider.html', 'hello') + return hello('World') + + .. versionadded:: 0.2 + + :param template_name: the name of the template + :param attribute: the name of the variable of macro to access + """ + return getattr(current_app.jinja_env.get_template(template_name).module, attribute) + + +def flash(message: str, category: str = "message") -> None: + """Flashes a message to the next request. In order to remove the + flashed message from the session and to display it to the user, + the template has to call :func:`get_flashed_messages`. + + .. versionchanged:: 0.3 + `category` parameter added. + + :param message: the message to be flashed. + :param category: the category for the message. The following values + are recommended: ``'message'`` for any kind of message, + ``'error'`` for errors, ``'info'`` for information + messages and ``'warning'`` for warnings. However any + kind of string can be used as category. + """ + # Original implementation: + # + # session.setdefault('_flashes', []).append((category, message)) + # + # This assumed that changes made to mutable structures in the session are + # always in sync with the session object, which is not true for session + # implementations that use external storage for keeping their keys/values. + flashes = session.get("_flashes", []) + flashes.append((category, message)) + session["_flashes"] = flashes + app = current_app._get_current_object() # type: ignore + message_flashed.send( + app, + _async_wrapper=app.ensure_sync, + message=message, + category=category, + ) + + +def get_flashed_messages( + with_categories: bool = False, category_filter: t.Iterable[str] = () +) -> list[str] | list[tuple[str, str]]: + """Pulls all flashed messages from the session and returns them. + Further calls in the same request to the function will return + the same messages. By default just the messages are returned, + but when `with_categories` is set to ``True``, the return value will + be a list of tuples in the form ``(category, message)`` instead. + + Filter the flashed messages to one or more categories by providing those + categories in `category_filter`. This allows rendering categories in + separate html blocks. The `with_categories` and `category_filter` + arguments are distinct: + + * `with_categories` controls whether categories are returned with message + text (``True`` gives a tuple, where ``False`` gives just the message text). + * `category_filter` filters the messages down to only those matching the + provided categories. + + See :doc:`/patterns/flashing` for examples. + + .. versionchanged:: 0.3 + `with_categories` parameter added. + + .. versionchanged:: 0.9 + `category_filter` parameter added. + + :param with_categories: set to ``True`` to also receive categories. + :param category_filter: filter of categories to limit return values. Only + categories in the list will be returned. + """ + flashes = request_ctx.flashes + if flashes is None: + flashes = session.pop("_flashes") if "_flashes" in session else [] + request_ctx.flashes = flashes + if category_filter: + flashes = list(filter(lambda f: f[0] in category_filter, flashes)) + if not with_categories: + return [x[1] for x in flashes] + return flashes + + +def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]: + if kwargs.get("max_age") is None: + kwargs["max_age"] = current_app.get_send_file_max_age + + kwargs.update( + environ=request.environ, + use_x_sendfile=current_app.config["USE_X_SENDFILE"], + response_class=current_app.response_class, + _root_path=current_app.root_path, + ) + return kwargs + + +def send_file( + path_or_file: os.PathLike[t.AnyStr] | str | t.IO[bytes], + mimetype: str | None = None, + as_attachment: bool = False, + download_name: str | None = None, + conditional: bool = True, + etag: bool | str = True, + last_modified: datetime | int | float | None = None, + max_age: None | (int | t.Callable[[str | None], int | None]) = None, +) -> Response: + """Send the contents of a file to the client. + + The first argument can be a file path or a file-like object. Paths + are preferred in most cases because Werkzeug can manage the file and + get extra information from the path. Passing a file-like object + requires that the file is opened in binary mode, and is mostly + useful when building a file in memory with :class:`io.BytesIO`. + + Never pass file paths provided by a user. The path is assumed to be + trusted, so a user could craft a path to access a file you didn't + intend. Use :func:`send_from_directory` to safely serve + user-requested paths from within a directory. + + If the WSGI server sets a ``file_wrapper`` in ``environ``, it is + used, otherwise Werkzeug's built-in wrapper is used. Alternatively, + if the HTTP server supports ``X-Sendfile``, configuring Flask with + ``USE_X_SENDFILE = True`` will tell the server to send the given + path, which is much more efficient than reading it in Python. + + :param path_or_file: The path to the file to send, relative to the + current working directory if a relative path is given. + Alternatively, a file-like object opened in binary mode. Make + sure the file pointer is seeked to the start of the data. + :param mimetype: The MIME type to send for the file. If not + provided, it will try to detect it from the file name. + :param as_attachment: Indicate to a browser that it should offer to + save the file instead of displaying it. + :param download_name: The default name browsers will use when saving + the file. Defaults to the passed file name. + :param conditional: Enable conditional and range responses based on + request headers. Requires passing a file path and ``environ``. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. + :param last_modified: The last modified time to send for the file, + in seconds. If not provided, it will try to detect it from the + file path. + :param max_age: How long the client should cache the file, in + seconds. If set, ``Cache-Control`` will be ``public``, otherwise + it will be ``no-cache`` to prefer conditional caching. + + .. versionchanged:: 2.0 + ``download_name`` replaces the ``attachment_filename`` + parameter. If ``as_attachment=False``, it is passed with + ``Content-Disposition: inline`` instead. + + .. versionchanged:: 2.0 + ``max_age`` replaces the ``cache_timeout`` parameter. + ``conditional`` is enabled and ``max_age`` is not set by + default. + + .. versionchanged:: 2.0 + ``etag`` replaces the ``add_etags`` parameter. It can be a + string to use instead of generating one. + + .. versionchanged:: 2.0 + Passing a file-like object that inherits from + :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather + than sending an empty file. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionchanged:: 1.1 + ``filename`` may be a :class:`~os.PathLike` object. + + .. versionchanged:: 1.1 + Passing a :class:`~io.BytesIO` object supports range requests. + + .. versionchanged:: 1.0.3 + Filenames are encoded with ASCII instead of Latin-1 for broader + compatibility with WSGI servers. + + .. versionchanged:: 1.0 + UTF-8 filenames as specified in :rfc:`2231` are supported. + + .. versionchanged:: 0.12 + The filename is no longer automatically inferred from file + objects. If you want to use automatic MIME and etag support, + pass a filename via ``filename_or_fp`` or + ``attachment_filename``. + + .. versionchanged:: 0.12 + ``attachment_filename`` is preferred over ``filename`` for MIME + detection. + + .. versionchanged:: 0.9 + ``cache_timeout`` defaults to + :meth:`Flask.get_send_file_max_age`. + + .. versionchanged:: 0.7 + MIME guessing and etag support for file-like objects was + removed because it was unreliable. Pass a filename if you are + able to, otherwise attach an etag yourself. + + .. versionchanged:: 0.5 + The ``add_etags``, ``cache_timeout`` and ``conditional`` + parameters were added. The default behavior is to add etags. + + .. versionadded:: 0.2 + """ + return werkzeug.utils.send_file( # type: ignore[return-value] + **_prepare_send_file_kwargs( + path_or_file=path_or_file, + environ=request.environ, + mimetype=mimetype, + as_attachment=as_attachment, + download_name=download_name, + conditional=conditional, + etag=etag, + last_modified=last_modified, + max_age=max_age, + ) + ) + + +def send_from_directory( + directory: os.PathLike[str] | str, + path: os.PathLike[str] | str, + **kwargs: t.Any, +) -> Response: + """Send a file from within a directory using :func:`send_file`. + + .. code-block:: python + + @app.route("/uploads/") + def download_file(name): + return send_from_directory( + app.config['UPLOAD_FOLDER'], name, as_attachment=True + ) + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + raises a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under, + relative to the current application's root path. This *must not* + be a value provided by the client, otherwise it becomes insecure. + :param path: The path to the file to send, relative to + ``directory``. + :param kwargs: Arguments to pass to :func:`send_file`. + + .. versionchanged:: 2.0 + ``path`` replaces the ``filename`` parameter. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionadded:: 0.5 + """ + return werkzeug.utils.send_from_directory( # type: ignore[return-value] + directory, path, **_prepare_send_file_kwargs(**kwargs) + ) + + +def get_root_path(import_name: str) -> str: + """Find the root path of a package, or the path that contains a + module. If it cannot be found, returns the current working + directory. + + Not to be confused with the value returned by :func:`find_package`. + + :meta private: + """ + # Module already imported and has a file attribute. Use that first. + mod = sys.modules.get(import_name) + + if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None: + return os.path.dirname(os.path.abspath(mod.__file__)) + + # Next attempt: check the loader. + try: + spec = importlib.util.find_spec(import_name) + + if spec is None: + raise ValueError + except (ImportError, ValueError): + loader = None + else: + loader = spec.loader + + # Loader does not exist or we're referring to an unloaded main + # module or a main module without path (interactive sessions), go + # with the current working directory. + if loader is None: + return os.getcwd() + + if hasattr(loader, "get_filename"): + filepath = loader.get_filename(import_name) # pyright: ignore + else: + # Fall back to imports. + __import__(import_name) + mod = sys.modules[import_name] + filepath = getattr(mod, "__file__", None) + + # If we don't have a file path it might be because it is a + # namespace package. In this case pick the root path from the + # first module that is contained in the package. + if filepath is None: + raise RuntimeError( + "No root path can be found for the provided module" + f" {import_name!r}. This can happen because the module" + " came from an import hook that does not provide file" + " name information or because it's a namespace package." + " In this case the root path needs to be explicitly" + " provided." + ) + + # filepath is import_name.py for a module, or __init__.py for a package. + return os.path.dirname(os.path.abspath(filepath)) # type: ignore[no-any-return] + + +@cache +def _split_blueprint_path(name: str) -> list[str]: + out: list[str] = [name] + + if "." in name: + out.extend(_split_blueprint_path(name.rpartition(".")[0])) + + return out diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/json/__init__.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/json/__init__.py new file mode 100644 index 0000000..c0941d0 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/json/__init__.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import json as _json +import typing as t + +from ..globals import current_app +from .provider import _default + +if t.TYPE_CHECKING: # pragma: no cover + from ..wrappers import Response + + +def dumps(obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dumps() ` + method, otherwise it will use :func:`json.dumps`. + + :param obj: The data to serialize. + :param kwargs: Arguments passed to the ``dumps`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dumps``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.dumps(obj, **kwargs) + + kwargs.setdefault("default", _default) + return _json.dumps(obj, **kwargs) + + +def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dump() ` + method, otherwise it will use :func:`json.dump`. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: Arguments passed to the ``dump`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dump``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + Writing to a binary file, and the ``encoding`` argument, will be + removed in Flask 2.1. + """ + if current_app: + current_app.json.dump(obj, fp, **kwargs) + else: + kwargs.setdefault("default", _default) + _json.dump(obj, fp, **kwargs) + + +def loads(s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.loads() ` + method, otherwise it will use :func:`json.loads`. + + :param s: Text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``loads`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.loads``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The data must be a + string or UTF-8 bytes. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.loads(s, **kwargs) + + return _json.loads(s, **kwargs) + + +def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.load() ` + method, otherwise it will use :func:`json.load`. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``load`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.load``, allowing an app to override + the behavior. + + .. versionchanged:: 2.2 + The ``app`` parameter will be removed in Flask 2.3. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The file must be text + mode, or binary mode with UTF-8 bytes. + """ + if current_app: + return current_app.json.load(fp, **kwargs) + + return _json.load(fp, **kwargs) + + +def jsonify(*args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. A dict or list returned from a view will be converted to a + JSON response automatically without needing to call this. + + This requires an active request or application context, and calls + :meth:`app.json.response() `. + + In debug mode, the output is formatted with indentation to make it + easier to read. This may also be controlled by the provider. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + + .. versionchanged:: 2.2 + Calls ``current_app.json.response``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 0.11 + Added support for serializing top-level arrays. This was a + security risk in ancient browsers. See :ref:`security-json`. + + .. versionadded:: 0.2 + """ + return current_app.json.response(*args, **kwargs) # type: ignore[return-value] diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6553a427f6e24ccc7978ce628fdc7c03e9b0748b GIT binary patch literal 6701 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 T{H6MuEXS`*{)a?Q&b$8xpxim7 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/json/__pycache__/tag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d2085cc41fffa21041363af1bbbeed5eb0bb584 GIT binary patch literal 13963 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 to an instance of the class. + + :param app: An application instance. This will be stored as a + :class:`weakref.proxy` on the :attr:`_app` attribute. + + .. versionadded:: 2.2 + """ + + def __init__(self, app: App) -> None: + self._app: App = weakref.proxy(app) + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + :param obj: The data to serialize. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: May be passed to the underlying JSON library. + """ + fp.write(self.dumps(obj, **kwargs)) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + :param s: Text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + return self.loads(fp.read(), **kwargs) + + def _prepare_response_obj( + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] + ) -> t.Any: + if args and kwargs: + raise TypeError("app.json.response() takes either args or kwargs, not both") + + if not args and not kwargs: + return None + + if len(args) == 1: + return args[0] + + return args or kwargs + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. + + The :func:`~flask.json.jsonify` function calls this method for + the current application. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + return self._app.response_class(self.dumps(obj), mimetype="application/json") + + +def _default(o: t.Any) -> t.Any: + if isinstance(o, date): + return http_date(o) + + if isinstance(o, (decimal.Decimal, uuid.UUID)): + return str(o) + + if dataclasses and dataclasses.is_dataclass(o): + return dataclasses.asdict(o) # type: ignore[arg-type] + + if hasattr(o, "__html__"): + return str(o.__html__()) + + raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") + + +class DefaultJSONProvider(JSONProvider): + """Provide JSON operations using Python's built-in :mod:`json` + library. Serializes the following additional data types: + + - :class:`datetime.datetime` and :class:`datetime.date` are + serialized to :rfc:`822` strings. This is the same as the HTTP + date format. + - :class:`uuid.UUID` is serialized to a string. + - :class:`dataclasses.dataclass` is passed to + :func:`dataclasses.asdict`. + - :class:`~markupsafe.Markup` (or any object with a ``__html__`` + method) will call the ``__html__`` method to get a string. + """ + + default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment] + """Apply this function to any object that :meth:`json.dumps` does + not know how to serialize. It should return a valid JSON type or + raise a ``TypeError``. + """ + + ensure_ascii = True + """Replace non-ASCII characters with escape sequences. This may be + more compatible with some clients, but can be disabled for better + performance and size. + """ + + sort_keys = True + """Sort the keys in any serialized dicts. This may be useful for + some caching situations, but can be disabled for better performance. + When enabled, keys must all be strings, they are not converted + before sorting. + """ + + compact: bool | None = None + """If ``True``, or ``None`` out of debug mode, the :meth:`response` + output will not add indentation, newlines, or spaces. If ``False``, + or ``None`` in debug mode, it will use a non-compact representation. + """ + + mimetype = "application/json" + """The mimetype set in :meth:`response`.""" + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON to a string. + + Keyword arguments are passed to :func:`json.dumps`. Sets some + parameter defaults from the :attr:`default`, + :attr:`ensure_ascii`, and :attr:`sort_keys` attributes. + + :param obj: The data to serialize. + :param kwargs: Passed to :func:`json.dumps`. + """ + kwargs.setdefault("default", self.default) + kwargs.setdefault("ensure_ascii", self.ensure_ascii) + kwargs.setdefault("sort_keys", self.sort_keys) + return json.dumps(obj, **kwargs) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON from a string or bytes. + + :param s: Text or UTF-8 bytes. + :param kwargs: Passed to :func:`json.loads`. + """ + return json.loads(s, **kwargs) + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with it. The response mimetype + will be "application/json" and can be changed with + :attr:`mimetype`. + + If :attr:`compact` is ``False`` or debug mode is enabled, the + output will be formatted to be easier to read. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + dump_args: dict[str, t.Any] = {} + + if (self.compact is None and self._app.debug) or self.compact is False: + dump_args.setdefault("indent", 2) + else: + dump_args.setdefault("separators", (",", ":")) + + return self._app.response_class( + f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype + ) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/json/tag.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/json/tag.py new file mode 100644 index 0000000..8dc3629 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/json/tag.py @@ -0,0 +1,327 @@ +""" +Tagged JSON +~~~~~~~~~~~ + +A compact representation for lossless serialization of non-standard JSON +types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this +to serialize the session data, but it may be useful in other places. It +can be extended to support other types. + +.. autoclass:: TaggedJSONSerializer + :members: + +.. autoclass:: JSONTag + :members: + +Let's see an example that adds support for +:class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so +to handle this we will dump the items as a list of ``[key, value]`` +pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to +identify the type. The session serializer processes dicts first, so +insert the new tag at the front of the order since ``OrderedDict`` must +be processed before ``dict``. + +.. code-block:: python + + from flask.json.tag import JSONTag + + class TagOrderedDict(JSONTag): + __slots__ = ('serializer',) + key = ' od' + + def check(self, value): + return isinstance(value, OrderedDict) + + def to_json(self, value): + return [[k, self.serializer.tag(v)] for k, v in iteritems(value)] + + def to_python(self, value): + return OrderedDict(value) + + app.session_interface.serializer.register(TagOrderedDict, index=0) +""" + +from __future__ import annotations + +import typing as t +from base64 import b64decode +from base64 import b64encode +from datetime import datetime +from uuid import UUID + +from markupsafe import Markup +from werkzeug.http import http_date +from werkzeug.http import parse_date + +from ..json import dumps +from ..json import loads + + +class JSONTag: + """Base class for defining type tags for :class:`TaggedJSONSerializer`.""" + + __slots__ = ("serializer",) + + #: The tag to mark the serialized object with. If empty, this tag is + #: only used as an intermediate step during tagging. + key: str = "" + + def __init__(self, serializer: TaggedJSONSerializer) -> None: + """Create a tagger for the given serializer.""" + self.serializer = serializer + + def check(self, value: t.Any) -> bool: + """Check if the given value should be tagged by this tag.""" + raise NotImplementedError + + def to_json(self, value: t.Any) -> t.Any: + """Convert the Python object to an object that is a valid JSON type. + The tag will be added later.""" + raise NotImplementedError + + def to_python(self, value: t.Any) -> t.Any: + """Convert the JSON representation back to the correct type. The tag + will already be removed.""" + raise NotImplementedError + + def tag(self, value: t.Any) -> dict[str, t.Any]: + """Convert the value to a valid JSON type and add the tag structure + around it.""" + return {self.key: self.to_json(value)} + + +class TagDict(JSONTag): + """Tag for 1-item dicts whose only key matches a registered tag. + + Internally, the dict key is suffixed with `__`, and the suffix is removed + when deserializing. + """ + + __slots__ = () + key = " di" + + def check(self, value: t.Any) -> bool: + return ( + isinstance(value, dict) + and len(value) == 1 + and next(iter(value)) in self.serializer.tags + ) + + def to_json(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {f"{key}__": self.serializer.tag(value[key])} + + def to_python(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {key[:-2]: value[key]} + + +class PassDict(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, dict) + + def to_json(self, value: t.Any) -> t.Any: + # JSON objects may only have string keys, so don't bother tagging the + # key here. + return {k: self.serializer.tag(v) for k, v in value.items()} + + tag = to_json + + +class TagTuple(JSONTag): + __slots__ = () + key = " t" + + def check(self, value: t.Any) -> bool: + return isinstance(value, tuple) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + def to_python(self, value: t.Any) -> t.Any: + return tuple(value) + + +class PassList(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, list) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + tag = to_json + + +class TagBytes(JSONTag): + __slots__ = () + key = " b" + + def check(self, value: t.Any) -> bool: + return isinstance(value, bytes) + + def to_json(self, value: t.Any) -> t.Any: + return b64encode(value).decode("ascii") + + def to_python(self, value: t.Any) -> t.Any: + return b64decode(value) + + +class TagMarkup(JSONTag): + """Serialize anything matching the :class:`~markupsafe.Markup` API by + having a ``__html__`` method to the result of that method. Always + deserializes to an instance of :class:`~markupsafe.Markup`.""" + + __slots__ = () + key = " m" + + def check(self, value: t.Any) -> bool: + return callable(getattr(value, "__html__", None)) + + def to_json(self, value: t.Any) -> t.Any: + return str(value.__html__()) + + def to_python(self, value: t.Any) -> t.Any: + return Markup(value) + + +class TagUUID(JSONTag): + __slots__ = () + key = " u" + + def check(self, value: t.Any) -> bool: + return isinstance(value, UUID) + + def to_json(self, value: t.Any) -> t.Any: + return value.hex + + def to_python(self, value: t.Any) -> t.Any: + return UUID(value) + + +class TagDateTime(JSONTag): + __slots__ = () + key = " d" + + def check(self, value: t.Any) -> bool: + return isinstance(value, datetime) + + def to_json(self, value: t.Any) -> t.Any: + return http_date(value) + + def to_python(self, value: t.Any) -> t.Any: + return parse_date(value) + + +class TaggedJSONSerializer: + """Serializer that uses a tag system to compactly represent objects that + are not JSON types. Passed as the intermediate serializer to + :class:`itsdangerous.Serializer`. + + The following extra types are supported: + + * :class:`dict` + * :class:`tuple` + * :class:`bytes` + * :class:`~markupsafe.Markup` + * :class:`~uuid.UUID` + * :class:`~datetime.datetime` + """ + + __slots__ = ("tags", "order") + + #: Tag classes to bind when creating the serializer. Other tags can be + #: added later using :meth:`~register`. + default_tags = [ + TagDict, + PassDict, + TagTuple, + PassList, + TagBytes, + TagMarkup, + TagUUID, + TagDateTime, + ] + + def __init__(self) -> None: + self.tags: dict[str, JSONTag] = {} + self.order: list[JSONTag] = [] + + for cls in self.default_tags: + self.register(cls) + + def register( + self, + tag_class: type[JSONTag], + force: bool = False, + index: int | None = None, + ) -> None: + """Register a new tag with this serializer. + + :param tag_class: tag class to register. Will be instantiated with this + serializer instance. + :param force: overwrite an existing tag. If false (default), a + :exc:`KeyError` is raised. + :param index: index to insert the new tag in the tag order. Useful when + the new tag is a special case of an existing tag. If ``None`` + (default), the tag is appended to the end of the order. + + :raise KeyError: if the tag key is already registered and ``force`` is + not true. + """ + tag = tag_class(self) + key = tag.key + + if key: + if not force and key in self.tags: + raise KeyError(f"Tag '{key}' is already registered.") + + self.tags[key] = tag + + if index is None: + self.order.append(tag) + else: + self.order.insert(index, tag) + + def tag(self, value: t.Any) -> t.Any: + """Convert a value to a tagged representation if necessary.""" + for tag in self.order: + if tag.check(value): + return tag.tag(value) + + return value + + def untag(self, value: dict[str, t.Any]) -> t.Any: + """Convert a tagged representation back to the original type.""" + if len(value) != 1: + return value + + key = next(iter(value)) + + if key not in self.tags: + return value + + return self.tags[key].to_python(value[key]) + + def _untag_scan(self, value: t.Any) -> t.Any: + if isinstance(value, dict): + # untag each item recursively + value = {k: self._untag_scan(v) for k, v in value.items()} + # untag the dict itself + value = self.untag(value) + elif isinstance(value, list): + # untag each item recursively + value = [self._untag_scan(item) for item in value] + + return value + + def dumps(self, value: t.Any) -> str: + """Tag the value and dump it to a compact JSON string.""" + return dumps(self.tag(value), separators=(",", ":")) + + def loads(self, value: str) -> t.Any: + """Load data from a JSON string and deserialized any tagged objects.""" + return self._untag_scan(loads(value)) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/logging.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/logging.py new file mode 100644 index 0000000..0cb8f43 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/logging.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import logging +import sys +import typing as t + +from werkzeug.local import LocalProxy + +from .globals import request + +if t.TYPE_CHECKING: # pragma: no cover + from .sansio.app import App + + +@LocalProxy +def wsgi_errors_stream() -> t.TextIO: + """Find the most appropriate error stream for the application. If a request + is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``. + + If you configure your own :class:`logging.StreamHandler`, you may want to + use this for the stream. If you are using file or dict configuration and + can't import this directly, you can refer to it as + ``ext://flask.logging.wsgi_errors_stream``. + """ + if request: + return request.environ["wsgi.errors"] # type: ignore[no-any-return] + + return sys.stderr + + +def has_level_handler(logger: logging.Logger) -> bool: + """Check if there is a handler in the logging chain that will handle the + given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`. + """ + level = logger.getEffectiveLevel() + current = logger + + while current: + if any(handler.level <= level for handler in current.handlers): + return True + + if not current.propagate: + break + + current = current.parent # type: ignore + + return False + + +#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format +#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``. +default_handler = logging.StreamHandler(wsgi_errors_stream) # type: ignore +default_handler.setFormatter( + logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s") +) + + +def create_logger(app: App) -> logging.Logger: + """Get the Flask app's logger and configure it if needed. + + The logger name will be the same as + :attr:`app.import_name `. + + When :attr:`~flask.Flask.debug` is enabled, set the logger level to + :data:`logging.DEBUG` if it is not set. + + If there is no handler for the logger's effective level, add a + :class:`~logging.StreamHandler` for + :func:`~flask.logging.wsgi_errors_stream` with a basic format. + """ + logger = logging.getLogger(app.name) + + if app.debug and not logger.level: + logger.setLevel(logging.DEBUG) + + if not has_level_handler(logger): + logger.addHandler(default_handler) + + return logger diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/py.typed b/web_viewer/.venv/lib/python3.12/site-packages/flask/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/README.md b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/README.md new file mode 100644 index 0000000..623ac19 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/README.md @@ -0,0 +1,6 @@ +# Sansio + +This folder contains code that can be used by alternative Flask +implementations, for example Quart. The code therefore cannot do any +IO, nor be part of a likely IO path. Finally this code cannot use the +Flask globals. diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ae708ed2294f640ac947e3e5194a3c5cc712c37 GIT binary patch literal 33720 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 H57z$$bpl2N literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ae7648c914e50519322965930fa234e860c3696 GIT binary patch literal 31221 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1afb2f8ddbe35568d75088e6cca98d5e63119b27 GIT binary patch literal 30234 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 z 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/app.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/app.py new file mode 100644 index 0000000..a2592fe --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/app.py @@ -0,0 +1,964 @@ +from __future__ import annotations + +import logging +import os +import sys +import typing as t +from datetime import timedelta +from itertools import chain + +from werkzeug.exceptions import Aborter +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequestKeyError +from werkzeug.routing import BuildError +from werkzeug.routing import Map +from werkzeug.routing import Rule +from werkzeug.sansio.response import Response +from werkzeug.utils import cached_property +from werkzeug.utils import redirect as _wz_redirect + +from .. import typing as ft +from ..config import Config +from ..config import ConfigAttribute +from ..ctx import _AppCtxGlobals +from ..helpers import _split_blueprint_path +from ..helpers import get_debug_flag +from ..json.provider import DefaultJSONProvider +from ..json.provider import JSONProvider +from ..logging import create_logger +from ..templating import DispatchingJinjaLoader +from ..templating import Environment +from .scaffold import _endpoint_from_view_func +from .scaffold import find_package +from .scaffold import Scaffold +from .scaffold import setupmethod + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.wrappers import Response as BaseResponse + + from ..testing import FlaskClient + from ..testing import FlaskCliRunner + from .blueprints import Blueprint + +T_shell_context_processor = t.TypeVar( + "T_shell_context_processor", bound=ft.ShellContextProcessorCallable +) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) +T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) +T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) + + +def _make_timedelta(value: timedelta | int | None) -> timedelta | None: + if value is None or isinstance(value, timedelta): + return value + + return timedelta(seconds=value) + + +class App(Scaffold): + """The flask object implements a WSGI application and acts as the central + object. It is passed the name of the module or package of the + application. Once it is created it will act as a central registry for + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the :file:`__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea of what + belongs to your application. This name is used to find resources + on the filesystem, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in :file:`yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplication.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. + + .. versionadded:: 0.11 + The `root_path` parameter was added. + + .. versionadded:: 1.0 + The ``host_matching`` and ``static_host`` parameters were added. + + .. versionadded:: 1.0 + The ``subdomain_matching`` parameter was added. Subdomain + matching needs to be enabled manually now. Setting + :data:`SERVER_NAME` does not implicitly enable it. + + :param import_name: the name of the application package + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: The folder with static files that is served at + ``static_url_path``. Relative to the application ``root_path`` + or an absolute path. Defaults to ``'static'``. + :param static_host: the host to use when adding the static route. + Defaults to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param host_matching: set ``url_map.host_matching`` attribute. + Defaults to False. + :param subdomain_matching: consider the subdomain relative to + :data:`SERVER_NAME` when matching routes. Defaults to False. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to ``True`` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. + :param root_path: The path to the root of the application files. + This should only be set manually when it can't be detected + automatically, such as for namespace packages. + """ + + #: The class of the object assigned to :attr:`aborter`, created by + #: :meth:`create_aborter`. That object is called by + #: :func:`flask.abort` to raise HTTP errors, and can be + #: called directly as well. + #: + #: Defaults to :class:`werkzeug.exceptions.Aborter`. + #: + #: .. versionadded:: 2.2 + aborter_class = Aborter + + #: The class that is used for the Jinja environment. + #: + #: .. versionadded:: 0.11 + jinja_environment = Environment + + #: The class that is used for the :data:`~flask.g` instance. + #: + #: Example use cases for a custom class: + #: + #: 1. Store arbitrary attributes on flask.g. + #: 2. Add a property for lazy per-request database connectors. + #: 3. Return None instead of AttributeError on unexpected attributes. + #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g. + #: + #: In Flask 0.9 this property was called `request_globals_class` but it + #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the + #: flask.g object is now application context scoped. + #: + #: .. versionadded:: 0.10 + app_ctx_globals_class = _AppCtxGlobals + + #: The class that is used for the ``config`` attribute of this app. + #: Defaults to :class:`~flask.Config`. + #: + #: Example use cases for a custom class: + #: + #: 1. Default values for certain config options. + #: 2. Access to config values through attributes in addition to keys. + #: + #: .. versionadded:: 0.11 + config_class = Config + + #: The testing flag. Set this to ``True`` to enable the test mode of + #: Flask extensions (and in the future probably also Flask itself). + #: For example this might activate test helpers that have an + #: additional runtime cost which should not be enabled by default. + #: + #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the + #: default it's implicitly enabled. + #: + #: This attribute can also be configured from the config with the + #: ``TESTING`` configuration key. Defaults to ``False``. + testing = ConfigAttribute[bool]("TESTING") + + #: If a secret key is set, cryptographic components can use this to + #: sign cookies and other things. Set this to a complex random value + #: when you want to use the secure cookie for instance. + #: + #: This attribute can also be configured from the config with the + #: :data:`SECRET_KEY` configuration key. Defaults to ``None``. + secret_key = ConfigAttribute[t.Union[str, bytes, None]]("SECRET_KEY") + + #: A :class:`~datetime.timedelta` which is used to set the expiration + #: date of a permanent session. The default is 31 days which makes a + #: permanent session survive for roughly one month. + #: + #: This attribute can also be configured from the config with the + #: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to + #: ``timedelta(days=31)`` + permanent_session_lifetime = ConfigAttribute[timedelta]( + "PERMANENT_SESSION_LIFETIME", + get_converter=_make_timedelta, # type: ignore[arg-type] + ) + + json_provider_class: type[JSONProvider] = DefaultJSONProvider + """A subclass of :class:`~flask.json.provider.JSONProvider`. An + instance is created and assigned to :attr:`app.json` when creating + the app. + + The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses + Python's built-in :mod:`json` library. A different provider can use + a different JSON library. + + .. versionadded:: 2.2 + """ + + #: Options that are passed to the Jinja environment in + #: :meth:`create_jinja_environment`. Changing these options after + #: the environment is created (accessing :attr:`jinja_env`) will + #: have no effect. + #: + #: .. versionchanged:: 1.1.0 + #: This is a ``dict`` instead of an ``ImmutableDict`` to allow + #: easier configuration. + #: + jinja_options: dict[str, t.Any] = {} + + #: The rule object to use for URL rules created. This is used by + #: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`. + #: + #: .. versionadded:: 0.7 + url_rule_class = Rule + + #: The map object to use for storing the URL rules and routing + #: configuration parameters. Defaults to :class:`werkzeug.routing.Map`. + #: + #: .. versionadded:: 1.1.0 + url_map_class = Map + + #: The :meth:`test_client` method creates an instance of this test + #: client class. Defaults to :class:`~flask.testing.FlaskClient`. + #: + #: .. versionadded:: 0.7 + test_client_class: type[FlaskClient] | None = None + + #: The :class:`~click.testing.CliRunner` subclass, by default + #: :class:`~flask.testing.FlaskCliRunner` that is used by + #: :meth:`test_cli_runner`. Its ``__init__`` method should take a + #: Flask app object as the first argument. + #: + #: .. versionadded:: 1.0 + test_cli_runner_class: type[FlaskCliRunner] | None = None + + default_config: dict[str, t.Any] + response_class: type[Response] + + def __init__( + self, + import_name: str, + static_url_path: str | None = None, + static_folder: str | os.PathLike[str] | None = "static", + static_host: str | None = None, + host_matching: bool = False, + subdomain_matching: bool = False, + template_folder: str | os.PathLike[str] | None = "templates", + instance_path: str | None = None, + instance_relative_config: bool = False, + root_path: str | None = None, + ) -> None: + super().__init__( + import_name=import_name, + static_folder=static_folder, + static_url_path=static_url_path, + template_folder=template_folder, + root_path=root_path, + ) + + if instance_path is None: + instance_path = self.auto_find_instance_path() + elif not os.path.isabs(instance_path): + raise ValueError( + "If an instance path is provided it must be absolute." + " A relative path was given instead." + ) + + #: Holds the path to the instance folder. + #: + #: .. versionadded:: 0.8 + self.instance_path = instance_path + + #: The configuration dictionary as :class:`Config`. This behaves + #: exactly like a regular dictionary but supports additional methods + #: to load a config from files. + self.config = self.make_config(instance_relative_config) + + #: An instance of :attr:`aborter_class` created by + #: :meth:`make_aborter`. This is called by :func:`flask.abort` + #: to raise HTTP errors, and can be called directly as well. + #: + #: .. versionadded:: 2.2 + #: Moved from ``flask.abort``, which calls this object. + self.aborter = self.make_aborter() + + self.json: JSONProvider = self.json_provider_class(self) + """Provides access to JSON methods. Functions in ``flask.json`` + will call methods on this provider when the application context + is active. Used for handling JSON requests and responses. + + An instance of :attr:`json_provider_class`. Can be customized by + changing that attribute on a subclass, or by assigning to this + attribute afterwards. + + The default, :class:`~flask.json.provider.DefaultJSONProvider`, + uses Python's built-in :mod:`json` library. A different provider + can use a different JSON library. + + .. versionadded:: 2.2 + """ + + #: A list of functions that are called by + #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a + #: :exc:`~werkzeug.routing.BuildError`. Each function is called + #: with ``error``, ``endpoint`` and ``values``. If a function + #: returns ``None`` or raises a ``BuildError``, it is skipped. + #: Otherwise, its return value is returned by ``url_for``. + #: + #: .. versionadded:: 0.9 + self.url_build_error_handlers: list[ + t.Callable[[Exception, str, dict[str, t.Any]], str] + ] = [] + + #: A list of functions that are called when the application context + #: is destroyed. Since the application context is also torn down + #: if the request ends this is the place to store code that disconnects + #: from databases. + #: + #: .. versionadded:: 0.9 + self.teardown_appcontext_funcs: list[ft.TeardownCallable] = [] + + #: A list of shell context processor functions that should be run + #: when a shell context is created. + #: + #: .. versionadded:: 0.11 + self.shell_context_processors: list[ft.ShellContextProcessorCallable] = [] + + #: Maps registered blueprint names to blueprint objects. The + #: dict retains the order the blueprints were registered in. + #: Blueprints can be registered multiple times, this dict does + #: not track how often they were attached. + #: + #: .. versionadded:: 0.7 + self.blueprints: dict[str, Blueprint] = {} + + #: a place where extensions can store application specific state. For + #: example this is where an extension could store database engines and + #: similar things. + #: + #: The key must match the name of the extension module. For example in + #: case of a "Flask-Foo" extension in `flask_foo`, the key would be + #: ``'foo'``. + #: + #: .. versionadded:: 0.7 + self.extensions: dict[str, t.Any] = {} + + #: The :class:`~werkzeug.routing.Map` for this instance. You can use + #: this to change the routing converters after the class was created + #: but before any routes are connected. Example:: + #: + #: from werkzeug.routing import BaseConverter + #: + #: class ListConverter(BaseConverter): + #: def to_python(self, value): + #: return value.split(',') + #: def to_url(self, values): + #: return ','.join(super(ListConverter, self).to_url(value) + #: for value in values) + #: + #: app = Flask(__name__) + #: app.url_map.converters['list'] = ListConverter + self.url_map = self.url_map_class(host_matching=host_matching) + + self.subdomain_matching = subdomain_matching + + # tracks internally if the application already handled at least one + # request. + self._got_first_request = False + + def _check_setup_finished(self, f_name: str) -> None: + if self._got_first_request: + raise AssertionError( + f"The setup method '{f_name}' can no longer be called" + " on the application. It has already handled its first" + " request, any changes will not be applied" + " consistently.\n" + "Make sure all imports, decorators, functions, etc." + " needed to set up the application are done before" + " running it." + ) + + @cached_property + def name(self) -> str: + """The name of the application. This is usually the import name + with the difference that it's guessed from the run file if the + import name is main. This name is used as a display name when + Flask needs the name of the application. It can be set and overridden + to change the value. + + .. versionadded:: 0.8 + """ + if self.import_name == "__main__": + fn: str | None = getattr(sys.modules["__main__"], "__file__", None) + if fn is None: + return "__main__" + return os.path.splitext(os.path.basename(fn))[0] + return self.import_name + + @cached_property + def logger(self) -> logging.Logger: + """A standard Python :class:`~logging.Logger` for the app, with + the same name as :attr:`name`. + + In debug mode, the logger's :attr:`~logging.Logger.level` will + be set to :data:`~logging.DEBUG`. + + If there are no handlers configured, a default handler will be + added. See :doc:`/logging` for more information. + + .. versionchanged:: 1.1.0 + The logger takes the same name as :attr:`name` rather than + hard-coding ``"flask.app"``. + + .. versionchanged:: 1.0.0 + Behavior was simplified. The logger is always named + ``"flask.app"``. The level is only set during configuration, + it doesn't check ``app.debug`` each time. Only one format is + used, not different ones depending on ``app.debug``. No + handlers are removed, and a handler is only added if no + handlers are already configured. + + .. versionadded:: 0.3 + """ + return create_logger(self) + + @cached_property + def jinja_env(self) -> Environment: + """The Jinja environment used to load templates. + + The environment is created the first time this property is + accessed. Changing :attr:`jinja_options` after that will have no + effect. + """ + return self.create_jinja_environment() + + def create_jinja_environment(self) -> Environment: + raise NotImplementedError() + + def make_config(self, instance_relative: bool = False) -> Config: + """Used to create the config attribute by the Flask constructor. + The `instance_relative` parameter is passed in from the constructor + of Flask (there named `instance_relative_config`) and indicates if + the config should be relative to the instance path or the root path + of the application. + + .. versionadded:: 0.8 + """ + root_path = self.root_path + if instance_relative: + root_path = self.instance_path + defaults = dict(self.default_config) + defaults["DEBUG"] = get_debug_flag() + return self.config_class(root_path, defaults) + + def make_aborter(self) -> Aborter: + """Create the object to assign to :attr:`aborter`. That object + is called by :func:`flask.abort` to raise HTTP errors, and can + be called directly as well. + + By default, this creates an instance of :attr:`aborter_class`, + which defaults to :class:`werkzeug.exceptions.Aborter`. + + .. versionadded:: 2.2 + """ + return self.aborter_class() + + def auto_find_instance_path(self) -> str: + """Tries to locate the instance path if it was not provided to the + constructor of the application class. It will basically calculate + the path to a folder named ``instance`` next to your main file or + the package. + + .. versionadded:: 0.8 + """ + prefix, package_path = find_package(self.import_name) + if prefix is None: + return os.path.join(package_path, "instance") + return os.path.join(prefix, "var", f"{self.name}-instance") + + def create_global_jinja_loader(self) -> DispatchingJinjaLoader: + """Creates the loader for the Jinja environment. Can be used to + override just the loader and keeping the rest unchanged. It's + discouraged to override this function. Instead one should override + the :meth:`jinja_loader` function instead. + + The global loader dispatches between the loaders of the application + and the individual blueprints. + + .. versionadded:: 0.7 + """ + return DispatchingJinjaLoader(self) + + def select_jinja_autoescape(self, filename: str) -> bool: + """Returns ``True`` if autoescaping should be active for the given + template name. If no template name is given, returns `True`. + + .. versionchanged:: 2.2 + Autoescaping is now enabled by default for ``.svg`` files. + + .. versionadded:: 0.5 + """ + if filename is None: + return True + return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg")) + + @property + def debug(self) -> bool: + """Whether debug mode is enabled. When using ``flask run`` to start the + development server, an interactive debugger will be shown for unhandled + exceptions, and the server will be reloaded when code changes. This maps to the + :data:`DEBUG` config key. It may not behave as expected if set late. + + **Do not enable debug mode when deploying in production.** + + Default: ``False`` + """ + return self.config["DEBUG"] # type: ignore[no-any-return] + + @debug.setter + def debug(self, value: bool) -> None: + self.config["DEBUG"] = value + + if self.config["TEMPLATES_AUTO_RELOAD"] is None: + self.jinja_env.auto_reload = value + + @setupmethod + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on the application. Keyword + arguments passed to this method will override the defaults set on the + blueprint. + + Calls the blueprint's :meth:`~flask.Blueprint.register` method after + recording the blueprint in the application's :attr:`blueprints`. + + :param blueprint: The blueprint to register. + :param url_prefix: Blueprint routes will be prefixed with this. + :param subdomain: Blueprint routes will match on this subdomain. + :param url_defaults: Blueprint routes will use these default values for + view arguments. + :param options: Additional keyword arguments are passed to + :class:`~flask.blueprints.BlueprintSetupState`. They can be + accessed in :meth:`~flask.Blueprint.record` callbacks. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 0.7 + """ + blueprint.register(self, options) + + def iter_blueprints(self) -> t.ValuesView[Blueprint]: + """Iterates over all blueprints by the order they were registered. + + .. versionadded:: 0.11 + """ + return self.blueprints.values() + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + options["endpoint"] = endpoint + methods = options.pop("methods", None) + + # if the methods are not given and the view_func object knows its + # methods we can use that instead. If neither exists, we go with + # a tuple of only ``GET`` as default. + if methods is None: + methods = getattr(view_func, "methods", None) or ("GET",) + if isinstance(methods, str): + raise TypeError( + "Allowed methods must be a list of strings, for" + ' example: @app.route(..., methods=["POST"])' + ) + methods = {item.upper() for item in methods} + + # Methods that should always be added + required_methods: set[str] = set(getattr(view_func, "required_methods", ())) + + # starting with Flask 0.8 the view_func object can disable and + # force-enable the automatic options handling. + if provide_automatic_options is None: + provide_automatic_options = getattr( + view_func, "provide_automatic_options", None + ) + + if provide_automatic_options is None: + if "OPTIONS" not in methods and self.config["PROVIDE_AUTOMATIC_OPTIONS"]: + provide_automatic_options = True + required_methods.add("OPTIONS") + else: + provide_automatic_options = False + + # Add the required methods now. + methods |= required_methods + + rule_obj = self.url_rule_class(rule, methods=methods, **options) + rule_obj.provide_automatic_options = provide_automatic_options # type: ignore[attr-defined] + + self.url_map.add(rule_obj) + if view_func is not None: + old_func = self.view_functions.get(endpoint) + if old_func is not None and old_func != view_func: + raise AssertionError( + "View function mapping is overwriting an existing" + f" endpoint function: {endpoint}" + ) + self.view_functions[endpoint] = view_func + + @setupmethod + def template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """A decorator that is used to register custom template filter. + You can specify a name for the filter, otherwise the function + name will be used. Example:: + + @app.template_filter() + def reverse(s): + return s[::-1] + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_filter( + self, f: ft.TemplateFilterCallable, name: str | None = None + ) -> None: + """Register a custom template filter. Works exactly like the + :meth:`template_filter` decorator. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + self.jinja_env.filters[name or f.__name__] = f + + @setupmethod + def template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: + """A decorator that is used to register custom template test. + You can specify a name for the test, otherwise the function + name will be used. Example:: + + @app.template_test() + def is_prime(n): + if n == 2: + return True + for i in range(2, int(math.ceil(math.sqrt(n))) + 1): + if n % i == 0: + return False + return True + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_test( + self, f: ft.TemplateTestCallable, name: str | None = None + ) -> None: + """Register a custom template test. Works exactly like the + :meth:`template_test` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + self.jinja_env.tests[name or f.__name__] = f + + @setupmethod + def template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: + """A decorator that is used to register a custom template global function. + You can specify a name for the global function, otherwise the function + name will be used. Example:: + + @app.template_global() + def double(n): + return 2 * n + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_global( + self, f: ft.TemplateGlobalCallable, name: str | None = None + ) -> None: + """Register a custom template global function. Works exactly like the + :meth:`template_global` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + self.jinja_env.globals[name or f.__name__] = f + + @setupmethod + def teardown_appcontext(self, f: T_teardown) -> T_teardown: + """Registers a function to be called when the application + context is popped. The application context is typically popped + after the request context for each request, at the end of CLI + commands, or after a manually pushed context ends. + + .. code-block:: python + + with app.app_context(): + ... + + When the ``with`` block exits (or ``ctx.pop()`` is called), the + teardown functions are called just before the app context is + made inactive. Since a request context typically also manages an + application context it would also be called when you pop a + request context. + + When a teardown function was called because of an unhandled + exception it will be passed an error object. If an + :meth:`errorhandler` is registered, it will handle the exception + and the teardown will not receive it. + + Teardown functions must avoid raising exceptions. If they + execute code that might fail they must surround that code with a + ``try``/``except`` block and log any errors. + + The return values of teardown functions are ignored. + + .. versionadded:: 0.9 + """ + self.teardown_appcontext_funcs.append(f) + return f + + @setupmethod + def shell_context_processor( + self, f: T_shell_context_processor + ) -> T_shell_context_processor: + """Registers a shell context processor function. + + .. versionadded:: 0.11 + """ + self.shell_context_processors.append(f) + return f + + def _find_error_handler( + self, e: Exception, blueprints: list[str] + ) -> ft.ErrorHandlerCallable | None: + """Return a registered error handler for an exception in this order: + blueprint handler for a specific code, app handler for a specific code, + blueprint handler for an exception class, app handler for an exception + class, or ``None`` if a suitable handler is not found. + """ + exc_class, code = self._get_exc_class_and_code(type(e)) + names = (*blueprints, None) + + for c in (code, None) if code is not None else (None,): + for name in names: + handler_map = self.error_handler_spec[name][c] + + if not handler_map: + continue + + for cls in exc_class.__mro__: + handler = handler_map.get(cls) + + if handler is not None: + return handler + return None + + def trap_http_exception(self, e: Exception) -> bool: + """Checks if an HTTP exception should be trapped or not. By default + this will return ``False`` for all exceptions except for a bad request + key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It + also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``. + + This is called for all HTTP exceptions raised by a view function. + If it returns ``True`` for any exception the error handler for this + exception is not called and it shows up as regular exception in the + traceback. This is helpful for debugging implicitly raised HTTP + exceptions. + + .. versionchanged:: 1.0 + Bad request errors are not trapped by default in debug mode. + + .. versionadded:: 0.8 + """ + if self.config["TRAP_HTTP_EXCEPTIONS"]: + return True + + trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"] + + # if unset, trap key errors in debug mode + if ( + trap_bad_request is None + and self.debug + and isinstance(e, BadRequestKeyError) + ): + return True + + if trap_bad_request: + return isinstance(e, BadRequest) + + return False + + def should_ignore_error(self, error: BaseException | None) -> bool: + """This is called to figure out if an error should be ignored + or not as far as the teardown system is concerned. If this + function returns ``True`` then the teardown handlers will not be + passed the error. + + .. versionadded:: 0.10 + """ + return False + + def redirect(self, location: str, code: int = 302) -> BaseResponse: + """Create a redirect response object. + + This is called by :func:`flask.redirect`, and can be called + directly as well. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + + .. versionadded:: 2.2 + Moved from ``flask.redirect``, which calls this method. + """ + return _wz_redirect( + location, + code=code, + Response=self.response_class, # type: ignore[arg-type] + ) + + def inject_url_defaults(self, endpoint: str, values: dict[str, t.Any]) -> None: + """Injects the URL defaults for the given endpoint directly into + the values dictionary passed. This is used internally and + automatically called on URL building. + + .. versionadded:: 0.7 + """ + names: t.Iterable[str | None] = (None,) + + # url_for may be called outside a request context, parse the + # passed endpoint instead of using request.blueprints. + if "." in endpoint: + names = chain( + names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0])) + ) + + for name in names: + if name in self.url_default_functions: + for func in self.url_default_functions[name]: + func(endpoint, values) + + def handle_url_build_error( + self, error: BuildError, endpoint: str, values: dict[str, t.Any] + ) -> str: + """Called by :meth:`.url_for` if a + :exc:`~werkzeug.routing.BuildError` was raised. If this returns + a value, it will be returned by ``url_for``, otherwise the error + will be re-raised. + + Each function in :attr:`url_build_error_handlers` is called with + ``error``, ``endpoint`` and ``values``. If a function returns + ``None`` or raises a ``BuildError``, it is skipped. Otherwise, + its return value is returned by ``url_for``. + + :param error: The active ``BuildError`` being handled. + :param endpoint: The endpoint being built. + :param values: The keyword arguments passed to ``url_for``. + """ + for handler in self.url_build_error_handlers: + try: + rv = handler(error, endpoint, values) + except BuildError as e: + # make error available outside except block + error = e + else: + if rv is not None: + return rv + + # Re-raise if called with an active exception, otherwise raise + # the passed in exception. + if error is sys.exc_info()[1]: + raise + + raise error diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/blueprints.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/blueprints.py new file mode 100644 index 0000000..4f912cc --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/blueprints.py @@ -0,0 +1,632 @@ +from __future__ import annotations + +import os +import typing as t +from collections import defaultdict +from functools import update_wrapper + +from .. import typing as ft +from .scaffold import _endpoint_from_view_func +from .scaffold import _sentinel +from .scaffold import Scaffold +from .scaffold import setupmethod + +if t.TYPE_CHECKING: # pragma: no cover + from .app import App + +DeferredSetupFunction = t.Callable[["BlueprintSetupState"], None] +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any]) +T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) +T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_context_processor = t.TypeVar( + "T_template_context_processor", bound=ft.TemplateContextProcessorCallable +) +T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) +T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) +T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) +T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) +T_url_value_preprocessor = t.TypeVar( + "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable +) + + +class BlueprintSetupState: + """Temporary holder object for registering a blueprint with the + application. An instance of this class is created by the + :meth:`~flask.Blueprint.make_setup_state` method and later passed + to all register callback functions. + """ + + def __init__( + self, + blueprint: Blueprint, + app: App, + options: t.Any, + first_registration: bool, + ) -> None: + #: a reference to the current application + self.app = app + + #: a reference to the blueprint that created this setup state. + self.blueprint = blueprint + + #: a dictionary with all options that were passed to the + #: :meth:`~flask.Flask.register_blueprint` method. + self.options = options + + #: as blueprints can be registered multiple times with the + #: application and not everything wants to be registered + #: multiple times on it, this attribute can be used to figure + #: out if the blueprint was registered in the past already. + self.first_registration = first_registration + + subdomain = self.options.get("subdomain") + if subdomain is None: + subdomain = self.blueprint.subdomain + + #: The subdomain that the blueprint should be active for, ``None`` + #: otherwise. + self.subdomain = subdomain + + url_prefix = self.options.get("url_prefix") + if url_prefix is None: + url_prefix = self.blueprint.url_prefix + #: The prefix that should be used for all URLs defined on the + #: blueprint. + self.url_prefix = url_prefix + + self.name = self.options.get("name", blueprint.name) + self.name_prefix = self.options.get("name_prefix", "") + + #: A dictionary with URL defaults that is added to each and every + #: URL that was defined with the blueprint. + self.url_defaults = dict(self.blueprint.url_values_defaults) + self.url_defaults.update(self.options.get("url_defaults", ())) + + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + **options: t.Any, + ) -> None: + """A helper method to register a rule (and optionally a view function) + to the application. The endpoint is automatically prefixed with the + blueprint's name. + """ + if self.url_prefix is not None: + if rule: + rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/"))) + else: + rule = self.url_prefix + options.setdefault("subdomain", self.subdomain) + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + defaults = self.url_defaults + if "defaults" in options: + defaults = dict(defaults, **options.pop("defaults")) + + self.app.add_url_rule( + rule, + f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), + view_func, + defaults=defaults, + **options, + ) + + +class Blueprint(Scaffold): + """Represents a blueprint, a collection of routes and other + app-related functions that can be registered on a real application + later. + + A blueprint is an object that allows defining application functions + without requiring an application object ahead of time. It uses the + same decorators as :class:`~flask.Flask`, but defers the need for an + application by recording them for later registration. + + Decorating a function with a blueprint creates a deferred function + that is called with :class:`~flask.blueprints.BlueprintSetupState` + when the blueprint is registered on an application. + + See :doc:`/blueprints` for more information. + + :param name: The name of the blueprint. Will be prepended to each + endpoint name. + :param import_name: The name of the blueprint package, usually + ``__name__``. This helps locate the ``root_path`` for the + blueprint. + :param static_folder: A folder with static files that should be + served by the blueprint's static route. The path is relative to + the blueprint's root path. Blueprint static files are disabled + by default. + :param static_url_path: The url to serve static files from. + Defaults to ``static_folder``. If the blueprint does not have + a ``url_prefix``, the app's static route will take precedence, + and the blueprint's static files won't be accessible. + :param template_folder: A folder with templates that should be added + to the app's template search path. The path is relative to the + blueprint's root path. Blueprint templates are disabled by + default. Blueprint templates have a lower precedence than those + in the app's templates folder. + :param url_prefix: A path to prepend to all of the blueprint's URLs, + to make them distinct from the rest of the app's routes. + :param subdomain: A subdomain that blueprint routes will match on by + default. + :param url_defaults: A dict of default values that blueprint routes + will receive by default. + :param root_path: By default, the blueprint will automatically set + this based on ``import_name``. In certain situations this + automatic detection can fail, so the path can be specified + manually instead. + + .. versionchanged:: 1.1.0 + Blueprints have a ``cli`` group to register nested CLI commands. + The ``cli_group`` parameter controls the name of the group under + the ``flask`` command. + + .. versionadded:: 0.7 + """ + + _got_registered_once = False + + def __init__( + self, + name: str, + import_name: str, + static_folder: str | os.PathLike[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | None = None, + url_prefix: str | None = None, + subdomain: str | None = None, + url_defaults: dict[str, t.Any] | None = None, + root_path: str | None = None, + cli_group: str | None = _sentinel, # type: ignore[assignment] + ): + super().__init__( + import_name=import_name, + static_folder=static_folder, + static_url_path=static_url_path, + template_folder=template_folder, + root_path=root_path, + ) + + if not name: + raise ValueError("'name' may not be empty.") + + if "." in name: + raise ValueError("'name' may not contain a dot '.' character.") + + self.name = name + self.url_prefix = url_prefix + self.subdomain = subdomain + self.deferred_functions: list[DeferredSetupFunction] = [] + + if url_defaults is None: + url_defaults = {} + + self.url_values_defaults = url_defaults + self.cli_group = cli_group + self._blueprints: list[tuple[Blueprint, dict[str, t.Any]]] = [] + + def _check_setup_finished(self, f_name: str) -> None: + if self._got_registered_once: + raise AssertionError( + f"The setup method '{f_name}' can no longer be called on the blueprint" + f" '{self.name}'. It has already been registered at least once, any" + " changes will not be applied consistently.\n" + "Make sure all imports, decorators, functions, etc. needed to set up" + " the blueprint are done before registering it." + ) + + @setupmethod + def record(self, func: DeferredSetupFunction) -> None: + """Registers a function that is called when the blueprint is + registered on the application. This function is called with the + state as argument as returned by the :meth:`make_setup_state` + method. + """ + self.deferred_functions.append(func) + + @setupmethod + def record_once(self, func: DeferredSetupFunction) -> None: + """Works like :meth:`record` but wraps the function in another + function that will ensure the function is only called once. If the + blueprint is registered a second time on the application, the + function passed is not called. + """ + + def wrapper(state: BlueprintSetupState) -> None: + if state.first_registration: + func(state) + + self.record(update_wrapper(wrapper, func)) + + def make_setup_state( + self, app: App, options: dict[str, t.Any], first_registration: bool = False + ) -> BlueprintSetupState: + """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` + object that is later passed to the register callback functions. + Subclasses can override this to return a subclass of the setup state. + """ + return BlueprintSetupState(self, app, options, first_registration) + + @setupmethod + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on this blueprint. Keyword + arguments passed to this method will override the defaults set + on the blueprint. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 2.0 + """ + if blueprint is self: + raise ValueError("Cannot register a blueprint on itself") + self._blueprints.append((blueprint, options)) + + def register(self, app: App, options: dict[str, t.Any]) -> None: + """Called by :meth:`Flask.register_blueprint` to register all + views and callbacks registered on the blueprint with the + application. Creates a :class:`.BlueprintSetupState` and calls + each :meth:`record` callback with it. + + :param app: The application this blueprint is being registered + with. + :param options: Keyword arguments forwarded from + :meth:`~Flask.register_blueprint`. + + .. versionchanged:: 2.3 + Nested blueprints now correctly apply subdomains. + + .. versionchanged:: 2.1 + Registering the same blueprint with the same name multiple + times is an error. + + .. versionchanged:: 2.0.1 + Nested blueprints are registered with their dotted name. + This allows different blueprints with the same name to be + nested at different locations. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + """ + name_prefix = options.get("name_prefix", "") + self_name = options.get("name", self.name) + name = f"{name_prefix}.{self_name}".lstrip(".") + + if name in app.blueprints: + bp_desc = "this" if app.blueprints[name] is self else "a different" + existing_at = f" '{name}'" if self_name != name else "" + + raise ValueError( + f"The name '{self_name}' is already registered for" + f" {bp_desc} blueprint{existing_at}. Use 'name=' to" + f" provide a unique name." + ) + + first_bp_registration = not any(bp is self for bp in app.blueprints.values()) + first_name_registration = name not in app.blueprints + + app.blueprints[name] = self + self._got_registered_once = True + state = self.make_setup_state(app, options, first_bp_registration) + + if self.has_static_folder: + state.add_url_rule( + f"{self.static_url_path}/", + view_func=self.send_static_file, # type: ignore[attr-defined] + endpoint="static", + ) + + # Merge blueprint data into parent. + if first_bp_registration or first_name_registration: + self._merge_blueprint_funcs(app, name) + + for deferred in self.deferred_functions: + deferred(state) + + cli_resolved_group = options.get("cli_group", self.cli_group) + + if self.cli.commands: + if cli_resolved_group is None: + app.cli.commands.update(self.cli.commands) + elif cli_resolved_group is _sentinel: + self.cli.name = name + app.cli.add_command(self.cli) + else: + self.cli.name = cli_resolved_group + app.cli.add_command(self.cli) + + for blueprint, bp_options in self._blueprints: + bp_options = bp_options.copy() + bp_url_prefix = bp_options.get("url_prefix") + bp_subdomain = bp_options.get("subdomain") + + if bp_subdomain is None: + bp_subdomain = blueprint.subdomain + + if state.subdomain is not None and bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + "." + state.subdomain + elif bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + elif state.subdomain is not None: + bp_options["subdomain"] = state.subdomain + + if bp_url_prefix is None: + bp_url_prefix = blueprint.url_prefix + + if state.url_prefix is not None and bp_url_prefix is not None: + bp_options["url_prefix"] = ( + state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") + ) + elif bp_url_prefix is not None: + bp_options["url_prefix"] = bp_url_prefix + elif state.url_prefix is not None: + bp_options["url_prefix"] = state.url_prefix + + bp_options["name_prefix"] = name + blueprint.register(app, bp_options) + + def _merge_blueprint_funcs(self, app: App, name: str) -> None: + def extend( + bp_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], + parent_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], + ) -> None: + for key, values in bp_dict.items(): + key = name if key is None else f"{name}.{key}" + parent_dict[key].extend(values) + + for key, value in self.error_handler_spec.items(): + key = name if key is None else f"{name}.{key}" + value = defaultdict( + dict, + { + code: {exc_class: func for exc_class, func in code_values.items()} + for code, code_values in value.items() + }, + ) + app.error_handler_spec[key] = value + + for endpoint, func in self.view_functions.items(): + app.view_functions[endpoint] = func + + extend(self.before_request_funcs, app.before_request_funcs) + extend(self.after_request_funcs, app.after_request_funcs) + extend( + self.teardown_request_funcs, + app.teardown_request_funcs, + ) + extend(self.url_default_functions, app.url_default_functions) + extend(self.url_value_preprocessors, app.url_value_preprocessors) + extend(self.template_context_processors, app.template_context_processors) + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for + full documentation. + + The URL rule is prefixed with the blueprint's URL prefix. The endpoint name, + used with :func:`url_for`, is prefixed with the blueprint's name. + """ + if endpoint and "." in endpoint: + raise ValueError("'endpoint' may not contain a dot '.' character.") + + if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: + raise ValueError("'view_func' name may not contain a dot '.' character.") + + self.record( + lambda s: s.add_url_rule( + rule, + endpoint, + view_func, + provide_automatic_options=provide_automatic_options, + **options, + ) + ) + + @setupmethod + def app_template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """Register a template filter, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_filter`. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_app_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_filter( + self, f: ft.TemplateFilterCallable, name: str | None = None + ) -> None: + """Register a template filter, available in any template rendered by the + application. Works like the :meth:`app_template_filter` decorator. Equivalent to + :meth:`.Flask.add_template_filter`. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.filters[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: + """Register a template test, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_test`. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_app_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_test( + self, f: ft.TemplateTestCallable, name: str | None = None + ) -> None: + """Register a template test, available in any template rendered by the + application. Works like the :meth:`app_template_test` decorator. Equivalent to + :meth:`.Flask.add_template_test`. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.tests[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: + """Register a template global, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_global`. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_app_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_global( + self, f: ft.TemplateGlobalCallable, name: str | None = None + ) -> None: + """Register a template global, available in any template rendered by the + application. Works like the :meth:`app_template_global` decorator. Equivalent to + :meth:`.Flask.add_template_global`. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.globals[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def before_app_request(self, f: T_before_request) -> T_before_request: + """Like :meth:`before_request`, but before every request, not only those handled + by the blueprint. Equivalent to :meth:`.Flask.before_request`. + """ + self.record_once( + lambda s: s.app.before_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def after_app_request(self, f: T_after_request) -> T_after_request: + """Like :meth:`after_request`, but after every request, not only those handled + by the blueprint. Equivalent to :meth:`.Flask.after_request`. + """ + self.record_once( + lambda s: s.app.after_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def teardown_app_request(self, f: T_teardown) -> T_teardown: + """Like :meth:`teardown_request`, but after every request, not only those + handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`. + """ + self.record_once( + lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_context_processor( + self, f: T_template_context_processor + ) -> T_template_context_processor: + """Like :meth:`context_processor`, but for templates rendered by every view, not + only by the blueprint. Equivalent to :meth:`.Flask.context_processor`. + """ + self.record_once( + lambda s: s.app.template_context_processors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_errorhandler( + self, code: type[Exception] | int + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Like :meth:`errorhandler`, but for every request, not only those handled by + the blueprint. Equivalent to :meth:`.Flask.errorhandler`. + """ + + def decorator(f: T_error_handler) -> T_error_handler: + def from_blueprint(state: BlueprintSetupState) -> None: + state.app.errorhandler(code)(f) + + self.record_once(from_blueprint) + return f + + return decorator + + @setupmethod + def app_url_value_preprocessor( + self, f: T_url_value_preprocessor + ) -> T_url_value_preprocessor: + """Like :meth:`url_value_preprocessor`, but for every request, not only those + handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`. + """ + self.record_once( + lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults: + """Like :meth:`url_defaults`, but for every request, not only those handled by + the blueprint. Equivalent to :meth:`.Flask.url_defaults`. + """ + self.record_once( + lambda s: s.app.url_default_functions.setdefault(None, []).append(f) + ) + return f diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/scaffold.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/scaffold.py new file mode 100644 index 0000000..0e96f15 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/sansio/scaffold.py @@ -0,0 +1,792 @@ +from __future__ import annotations + +import importlib.util +import os +import pathlib +import sys +import typing as t +from collections import defaultdict +from functools import update_wrapper + +from jinja2 import BaseLoader +from jinja2 import FileSystemLoader +from werkzeug.exceptions import default_exceptions +from werkzeug.exceptions import HTTPException +from werkzeug.utils import cached_property + +from .. import typing as ft +from ..helpers import get_root_path +from ..templating import _default_template_ctx_processor + +if t.TYPE_CHECKING: # pragma: no cover + from click import Group + +# a singleton sentinel value for parameter defaults +_sentinel = object() + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any]) +T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) +T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_context_processor = t.TypeVar( + "T_template_context_processor", bound=ft.TemplateContextProcessorCallable +) +T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) +T_url_value_preprocessor = t.TypeVar( + "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable +) +T_route = t.TypeVar("T_route", bound=ft.RouteCallable) + + +def setupmethod(f: F) -> F: + f_name = f.__name__ + + def wrapper_func(self: Scaffold, *args: t.Any, **kwargs: t.Any) -> t.Any: + self._check_setup_finished(f_name) + return f(self, *args, **kwargs) + + return t.cast(F, update_wrapper(wrapper_func, f)) + + +class Scaffold: + """Common behavior shared between :class:`~flask.Flask` and + :class:`~flask.blueprints.Blueprint`. + + :param import_name: The import name of the module where this object + is defined. Usually :attr:`__name__` should be used. + :param static_folder: Path to a folder of static files to serve. + If this is set, a static route will be added. + :param static_url_path: URL prefix for the static route. + :param template_folder: Path to a folder containing template files. + for rendering. If this is set, a Jinja loader will be added. + :param root_path: The path that static, template, and resource files + are relative to. Typically not set, it is discovered based on + the ``import_name``. + + .. versionadded:: 2.0 + """ + + cli: Group + name: str + _static_folder: str | None = None + _static_url_path: str | None = None + + def __init__( + self, + import_name: str, + static_folder: str | os.PathLike[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | None = None, + root_path: str | None = None, + ): + #: The name of the package or module that this object belongs + #: to. Do not change this once it is set by the constructor. + self.import_name = import_name + + self.static_folder = static_folder + self.static_url_path = static_url_path + + #: The path to the templates folder, relative to + #: :attr:`root_path`, to add to the template loader. ``None`` if + #: templates should not be added. + self.template_folder = template_folder + + if root_path is None: + root_path = get_root_path(self.import_name) + + #: Absolute path to the package on the filesystem. Used to look + #: up resources contained in the package. + self.root_path = root_path + + #: A dictionary mapping endpoint names to view functions. + #: + #: To register a view function, use the :meth:`route` decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.view_functions: dict[str, ft.RouteCallable] = {} + + #: A data structure of registered error handlers, in the format + #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is + #: the name of a blueprint the handlers are active for, or + #: ``None`` for all requests. The ``code`` key is the HTTP + #: status code for ``HTTPException``, or ``None`` for + #: other exceptions. The innermost dictionary maps exception + #: classes to handler functions. + #: + #: To register an error handler, use the :meth:`errorhandler` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.error_handler_spec: dict[ + ft.AppOrBlueprintKey, + dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]], + ] = defaultdict(lambda: defaultdict(dict)) + + #: A data structure of functions to call at the beginning of + #: each request, in the format ``{scope: [functions]}``. The + #: ``scope`` key is the name of a blueprint the functions are + #: active for, or ``None`` for all requests. + #: + #: To register a function, use the :meth:`before_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.before_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable] + ] = defaultdict(list) + + #: A data structure of functions to call at the end of each + #: request, in the format ``{scope: [functions]}``. The + #: ``scope`` key is the name of a blueprint the functions are + #: active for, or ``None`` for all requests. + #: + #: To register a function, use the :meth:`after_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.after_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.AfterRequestCallable[t.Any]] + ] = defaultdict(list) + + #: A data structure of functions to call at the end of each + #: request even if an exception is raised, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`teardown_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.teardown_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.TeardownCallable] + ] = defaultdict(list) + + #: A data structure of functions to call to pass extra context + #: values when rendering templates, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`context_processor` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.template_context_processors: dict[ + ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable] + ] = defaultdict(list, {None: [_default_template_ctx_processor]}) + + #: A data structure of functions to call to modify the keyword + #: arguments passed to the view function, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the + #: :meth:`url_value_preprocessor` decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.url_value_preprocessors: dict[ + ft.AppOrBlueprintKey, + list[ft.URLValuePreprocessorCallable], + ] = defaultdict(list) + + #: A data structure of functions to call to modify the keyword + #: arguments when generating URLs, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`url_defaults` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.url_default_functions: dict[ + ft.AppOrBlueprintKey, list[ft.URLDefaultCallable] + ] = defaultdict(list) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.name!r}>" + + def _check_setup_finished(self, f_name: str) -> None: + raise NotImplementedError + + @property + def static_folder(self) -> str | None: + """The absolute path to the configured static folder. ``None`` + if no static folder is set. + """ + if self._static_folder is not None: + return os.path.join(self.root_path, self._static_folder) + else: + return None + + @static_folder.setter + def static_folder(self, value: str | os.PathLike[str] | None) -> None: + if value is not None: + value = os.fspath(value).rstrip(r"\/") + + self._static_folder = value + + @property + def has_static_folder(self) -> bool: + """``True`` if :attr:`static_folder` is set. + + .. versionadded:: 0.5 + """ + return self.static_folder is not None + + @property + def static_url_path(self) -> str | None: + """The URL prefix that the static route will be accessible from. + + If it was not configured during init, it is derived from + :attr:`static_folder`. + """ + if self._static_url_path is not None: + return self._static_url_path + + if self.static_folder is not None: + basename = os.path.basename(self.static_folder) + return f"/{basename}".rstrip("/") + + return None + + @static_url_path.setter + def static_url_path(self, value: str | None) -> None: + if value is not None: + value = value.rstrip("/") + + self._static_url_path = value + + @cached_property + def jinja_loader(self) -> BaseLoader | None: + """The Jinja loader for this object's templates. By default this + is a class :class:`jinja2.loaders.FileSystemLoader` to + :attr:`template_folder` if it is set. + + .. versionadded:: 0.5 + """ + if self.template_folder is not None: + return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) + else: + return None + + def _method_route( + self, + method: str, + rule: str, + options: dict[str, t.Any], + ) -> t.Callable[[T_route], T_route]: + if "methods" in options: + raise TypeError("Use the 'route' decorator to use the 'methods' argument.") + + return self.route(rule, methods=[method], **options) + + @setupmethod + def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["GET"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("GET", rule, options) + + @setupmethod + def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["POST"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("POST", rule, options) + + @setupmethod + def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["PUT"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PUT", rule, options) + + @setupmethod + def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["DELETE"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("DELETE", rule, options) + + @setupmethod + def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["PATCH"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PATCH", rule, options) + + @setupmethod + def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Decorate a view function to register it with the given URL + rule and options. Calls :meth:`add_url_rule`, which has more + details about the implementation. + + .. code-block:: python + + @app.route("/") + def index(): + return "Hello, World!" + + See :ref:`url-route-registrations`. + + The endpoint name for the route defaults to the name of the view + function if the ``endpoint`` parameter isn't passed. + + The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and + ``OPTIONS`` are added automatically. + + :param rule: The URL rule string. + :param options: Extra options passed to the + :class:`~werkzeug.routing.Rule` object. + """ + + def decorator(f: T_route) -> T_route: + endpoint = options.pop("endpoint", None) + self.add_url_rule(rule, endpoint, f, **options) + return f + + return decorator + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + """Register a rule for routing incoming requests and building + URLs. The :meth:`route` decorator is a shortcut to call this + with the ``view_func`` argument. These are equivalent: + + .. code-block:: python + + @app.route("/") + def index(): + ... + + .. code-block:: python + + def index(): + ... + + app.add_url_rule("/", view_func=index) + + See :ref:`url-route-registrations`. + + The endpoint name for the route defaults to the name of the view + function if the ``endpoint`` parameter isn't passed. An error + will be raised if a function has already been registered for the + endpoint. + + The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is + always added automatically, and ``OPTIONS`` is added + automatically by default. + + ``view_func`` does not necessarily need to be passed, but if the + rule should participate in routing an endpoint name must be + associated with a view function at some point with the + :meth:`endpoint` decorator. + + .. code-block:: python + + app.add_url_rule("/", endpoint="index") + + @app.endpoint("index") + def index(): + ... + + If ``view_func`` has a ``required_methods`` attribute, those + methods are added to the passed and automatic methods. If it + has a ``provide_automatic_methods`` attribute, it is used as the + default if the parameter is not passed. + + :param rule: The URL rule string. + :param endpoint: The endpoint name to associate with the rule + and view function. Used when routing and building URLs. + Defaults to ``view_func.__name__``. + :param view_func: The view function to associate with the + endpoint name. + :param provide_automatic_options: Add the ``OPTIONS`` method and + respond to ``OPTIONS`` requests automatically. + :param options: Extra options passed to the + :class:`~werkzeug.routing.Rule` object. + """ + raise NotImplementedError + + @setupmethod + def endpoint(self, endpoint: str) -> t.Callable[[F], F]: + """Decorate a view function to register it for the given + endpoint. Used if a rule is added without a ``view_func`` with + :meth:`add_url_rule`. + + .. code-block:: python + + app.add_url_rule("/ex", endpoint="example") + + @app.endpoint("example") + def example(): + ... + + :param endpoint: The endpoint name to associate with the view + function. + """ + + def decorator(f: F) -> F: + self.view_functions[endpoint] = f + return f + + return decorator + + @setupmethod + def before_request(self, f: T_before_request) -> T_before_request: + """Register a function to run before each request. + + For example, this can be used to open a database connection, or + to load the logged in user from the session. + + .. code-block:: python + + @app.before_request + def load_user(): + if "user_id" in session: + g.user = db.session.get(session["user_id"]) + + The function will be called without any arguments. If it returns + a non-``None`` value, the value is handled as if it was the + return value from the view, and further request handling is + stopped. + + This is available on both app and blueprint objects. When used on an app, this + executes before every request. When used on a blueprint, this executes before + every request that the blueprint handles. To register with a blueprint and + execute before every request, use :meth:`.Blueprint.before_app_request`. + """ + self.before_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def after_request(self, f: T_after_request) -> T_after_request: + """Register a function to run after each request to this object. + + The function is called with the response object, and must return + a response object. This allows the functions to modify or + replace the response before it is sent. + + If a function raises an exception, any remaining + ``after_request`` functions will not be called. Therefore, this + should not be used for actions that must execute, such as to + close resources. Use :meth:`teardown_request` for that. + + This is available on both app and blueprint objects. When used on an app, this + executes after every request. When used on a blueprint, this executes after + every request that the blueprint handles. To register with a blueprint and + execute after every request, use :meth:`.Blueprint.after_app_request`. + """ + self.after_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def teardown_request(self, f: T_teardown) -> T_teardown: + """Register a function to be called when the request context is + popped. Typically this happens at the end of each request, but + contexts may be pushed manually as well during testing. + + .. code-block:: python + + with app.test_request_context(): + ... + + When the ``with`` block exits (or ``ctx.pop()`` is called), the + teardown functions are called just before the request context is + made inactive. + + When a teardown function was called because of an unhandled + exception it will be passed an error object. If an + :meth:`errorhandler` is registered, it will handle the exception + and the teardown will not receive it. + + Teardown functions must avoid raising exceptions. If they + execute code that might fail they must surround that code with a + ``try``/``except`` block and log any errors. + + The return values of teardown functions are ignored. + + This is available on both app and blueprint objects. When used on an app, this + executes after every request. When used on a blueprint, this executes after + every request that the blueprint handles. To register with a blueprint and + execute after every request, use :meth:`.Blueprint.teardown_app_request`. + """ + self.teardown_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def context_processor( + self, + f: T_template_context_processor, + ) -> T_template_context_processor: + """Registers a template context processor function. These functions run before + rendering a template. The keys of the returned dict are added as variables + available in the template. + + This is available on both app and blueprint objects. When used on an app, this + is called for every rendered template. When used on a blueprint, this is called + for templates rendered from the blueprint's views. To register with a blueprint + and affect every template, use :meth:`.Blueprint.app_context_processor`. + """ + self.template_context_processors[None].append(f) + return f + + @setupmethod + def url_value_preprocessor( + self, + f: T_url_value_preprocessor, + ) -> T_url_value_preprocessor: + """Register a URL value preprocessor function for all view + functions in the application. These functions will be called before the + :meth:`before_request` functions. + + The function can modify the values captured from the matched url before + they are passed to the view. For example, this can be used to pop a + common language code value and place it in ``g`` rather than pass it to + every view. + + The function is passed the endpoint name and values dict. The return + value is ignored. + + This is available on both app and blueprint objects. When used on an app, this + is called for every request. When used on a blueprint, this is called for + requests that the blueprint handles. To register with a blueprint and affect + every request, use :meth:`.Blueprint.app_url_value_preprocessor`. + """ + self.url_value_preprocessors[None].append(f) + return f + + @setupmethod + def url_defaults(self, f: T_url_defaults) -> T_url_defaults: + """Callback function for URL defaults for all view functions of the + application. It's called with the endpoint and values and should + update the values passed in place. + + This is available on both app and blueprint objects. When used on an app, this + is called for every request. When used on a blueprint, this is called for + requests that the blueprint handles. To register with a blueprint and affect + every request, use :meth:`.Blueprint.app_url_defaults`. + """ + self.url_default_functions[None].append(f) + return f + + @setupmethod + def errorhandler( + self, code_or_exception: type[Exception] | int + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Register a function to handle errors by code or exception class. + + A decorator that is used to register a function given an + error code. Example:: + + @app.errorhandler(404) + def page_not_found(error): + return 'This page does not exist', 404 + + You can also register handlers for arbitrary exceptions:: + + @app.errorhandler(DatabaseError) + def special_exception_handler(error): + return 'Database connection failed', 500 + + This is available on both app and blueprint objects. When used on an app, this + can handle errors from every request. When used on a blueprint, this can handle + errors from requests that the blueprint handles. To register with a blueprint + and affect every request, use :meth:`.Blueprint.app_errorhandler`. + + .. versionadded:: 0.7 + Use :meth:`register_error_handler` instead of modifying + :attr:`error_handler_spec` directly, for application wide error + handlers. + + .. versionadded:: 0.7 + One can now additionally also register custom exception types + that do not necessarily have to be a subclass of the + :class:`~werkzeug.exceptions.HTTPException` class. + + :param code_or_exception: the code as integer for the handler, or + an arbitrary exception + """ + + def decorator(f: T_error_handler) -> T_error_handler: + self.register_error_handler(code_or_exception, f) + return f + + return decorator + + @setupmethod + def register_error_handler( + self, + code_or_exception: type[Exception] | int, + f: ft.ErrorHandlerCallable, + ) -> None: + """Alternative error attach function to the :meth:`errorhandler` + decorator that is more straightforward to use for non decorator + usage. + + .. versionadded:: 0.7 + """ + exc_class, code = self._get_exc_class_and_code(code_or_exception) + self.error_handler_spec[None][code][exc_class] = f + + @staticmethod + def _get_exc_class_and_code( + exc_class_or_code: type[Exception] | int, + ) -> tuple[type[Exception], int | None]: + """Get the exception class being handled. For HTTP status codes + or ``HTTPException`` subclasses, return both the exception and + status code. + + :param exc_class_or_code: Any exception class, or an HTTP status + code as an integer. + """ + exc_class: type[Exception] + + if isinstance(exc_class_or_code, int): + try: + exc_class = default_exceptions[exc_class_or_code] + except KeyError: + raise ValueError( + f"'{exc_class_or_code}' is not a recognized HTTP" + " error code. Use a subclass of HTTPException with" + " that code instead." + ) from None + else: + exc_class = exc_class_or_code + + if isinstance(exc_class, Exception): + raise TypeError( + f"{exc_class!r} is an instance, not a class. Handlers" + " can only be registered for Exception classes or HTTP" + " error codes." + ) + + if not issubclass(exc_class, Exception): + raise ValueError( + f"'{exc_class.__name__}' is not a subclass of Exception." + " Handlers can only be registered for Exception classes" + " or HTTP error codes." + ) + + if issubclass(exc_class, HTTPException): + return exc_class, exc_class.code + else: + return exc_class, None + + +def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str: + """Internal helper that returns the default endpoint for a given + function. This always is the function name. + """ + assert view_func is not None, "expected view func if endpoint is not provided." + return view_func.__name__ + + +def _find_package_path(import_name: str) -> str: + """Find the path that contains the package or module.""" + root_mod_name, _, _ = import_name.partition(".") + + try: + root_spec = importlib.util.find_spec(root_mod_name) + + if root_spec is None: + raise ValueError("not found") + except (ImportError, ValueError): + # ImportError: the machinery told us it does not exist + # ValueError: + # - the module name was invalid + # - the module name is __main__ + # - we raised `ValueError` due to `root_spec` being `None` + return os.getcwd() + + if root_spec.submodule_search_locations: + if root_spec.origin is None or root_spec.origin == "namespace": + # namespace package + package_spec = importlib.util.find_spec(import_name) + + if package_spec is not None and package_spec.submodule_search_locations: + # Pick the path in the namespace that contains the submodule. + package_path = pathlib.Path( + os.path.commonpath(package_spec.submodule_search_locations) + ) + search_location = next( + location + for location in root_spec.submodule_search_locations + if package_path.is_relative_to(location) + ) + else: + # Pick the first path. + search_location = root_spec.submodule_search_locations[0] + + return os.path.dirname(search_location) + else: + # package with __init__.py + return os.path.dirname(os.path.dirname(root_spec.origin)) + else: + # module + return os.path.dirname(root_spec.origin) # type: ignore[type-var, return-value] + + +def find_package(import_name: str) -> tuple[str | None, str]: + """Find the prefix that a package is installed under, and the path + that it would be imported from. + + The prefix is the directory containing the standard directory + hierarchy (lib, bin, etc.). If the package is not installed to the + system (:attr:`sys.prefix`) or a virtualenv (``site-packages``), + ``None`` is returned. + + The path is the entry in :attr:`sys.path` that contains the package + for import. If the package is not installed, it's assumed that the + package was imported from the current working directory. + """ + package_path = _find_package_path(import_name) + py_prefix = os.path.abspath(sys.prefix) + + # installed to the system + if pathlib.PurePath(package_path).is_relative_to(py_prefix): + return py_prefix, package_path + + site_parent, site_folder = os.path.split(package_path) + + # installed to a virtualenv + if site_folder.lower() == "site-packages": + parent, folder = os.path.split(site_parent) + + # Windows (prefix/lib/site-packages) + if folder.lower() == "lib": + return parent, package_path + + # Unix (prefix/lib/pythonX.Y/site-packages) + if os.path.basename(parent).lower() == "lib": + return os.path.dirname(parent), package_path + + # something else (prefix/site-packages) + return site_parent, package_path + + # not installed + return None, package_path diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/sessions.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/sessions.py new file mode 100644 index 0000000..0a357d9 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/sessions.py @@ -0,0 +1,399 @@ +from __future__ import annotations + +import collections.abc as c +import hashlib +import typing as t +from collections.abc import MutableMapping +from datetime import datetime +from datetime import timezone + +from itsdangerous import BadSignature +from itsdangerous import URLSafeTimedSerializer +from werkzeug.datastructures import CallbackDict + +from .json.tag import TaggedJSONSerializer + +if t.TYPE_CHECKING: # pragma: no cover + import typing_extensions as te + + from .app import Flask + from .wrappers import Request + from .wrappers import Response + + +class SessionMixin(MutableMapping[str, t.Any]): + """Expands a basic dictionary with session attributes.""" + + @property + def permanent(self) -> bool: + """This reflects the ``'_permanent'`` key in the dict.""" + return self.get("_permanent", False) + + @permanent.setter + def permanent(self, value: bool) -> None: + self["_permanent"] = bool(value) + + #: Some implementations can detect whether a session is newly + #: created, but that is not guaranteed. Use with caution. The mixin + # default is hard-coded ``False``. + new = False + + #: Some implementations can detect changes to the session and set + #: this when that happens. The mixin default is hard coded to + #: ``True``. + modified = True + + #: Some implementations can detect when session data is read or + #: written and set this when that happens. The mixin default is hard + #: coded to ``True``. + accessed = True + + +class SecureCookieSession(CallbackDict[str, t.Any], SessionMixin): + """Base class for sessions based on signed cookies. + + This session backend will set the :attr:`modified` and + :attr:`accessed` attributes. It cannot reliably track whether a + session is new (vs. empty), so :attr:`new` remains hard coded to + ``False``. + """ + + #: When data is changed, this is set to ``True``. Only the session + #: dictionary itself is tracked; if the session contains mutable + #: data (for example a nested dict) then this must be set to + #: ``True`` manually when modifying that data. The session cookie + #: will only be written to the response if this is ``True``. + modified = False + + #: When data is read or written, this is set to ``True``. Used by + # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie`` + #: header, which allows caching proxies to cache different pages for + #: different users. + accessed = False + + def __init__( + self, + initial: c.Mapping[str, t.Any] | c.Iterable[tuple[str, t.Any]] | None = None, + ) -> None: + def on_update(self: te.Self) -> None: + self.modified = True + self.accessed = True + + super().__init__(initial, on_update) + + def __getitem__(self, key: str) -> t.Any: + self.accessed = True + return super().__getitem__(key) + + def get(self, key: str, default: t.Any = None) -> t.Any: + self.accessed = True + return super().get(key, default) + + def setdefault(self, key: str, default: t.Any = None) -> t.Any: + self.accessed = True + return super().setdefault(key, default) + + +class NullSession(SecureCookieSession): + """Class used to generate nicer error messages if sessions are not + available. Will still allow read-only access to the empty session + but fail on setting. + """ + + def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + raise RuntimeError( + "The session is unavailable because no secret " + "key was set. Set the secret_key on the " + "application to something unique and secret." + ) + + __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # noqa: B950 + del _fail + + +class SessionInterface: + """The basic interface you have to implement in order to replace the + default session interface which uses werkzeug's securecookie + implementation. The only methods you have to implement are + :meth:`open_session` and :meth:`save_session`, the others have + useful defaults which you don't need to change. + + The session object returned by the :meth:`open_session` method has to + provide a dictionary like interface plus the properties and methods + from the :class:`SessionMixin`. We recommend just subclassing a dict + and adding that mixin:: + + class Session(dict, SessionMixin): + pass + + If :meth:`open_session` returns ``None`` Flask will call into + :meth:`make_null_session` to create a session that acts as replacement + if the session support cannot work because some requirement is not + fulfilled. The default :class:`NullSession` class that is created + will complain that the secret key was not set. + + To replace the session interface on an application all you have to do + is to assign :attr:`flask.Flask.session_interface`:: + + app = Flask(__name__) + app.session_interface = MySessionInterface() + + Multiple requests with the same session may be sent and handled + concurrently. When implementing a new session interface, consider + whether reads or writes to the backing store must be synchronized. + There is no guarantee on the order in which the session for each + request is opened or saved, it will occur in the order that requests + begin and end processing. + + .. versionadded:: 0.8 + """ + + #: :meth:`make_null_session` will look here for the class that should + #: be created when a null session is requested. Likewise the + #: :meth:`is_null_session` method will perform a typecheck against + #: this type. + null_session_class = NullSession + + #: A flag that indicates if the session interface is pickle based. + #: This can be used by Flask extensions to make a decision in regards + #: to how to deal with the session object. + #: + #: .. versionadded:: 0.10 + pickle_based = False + + def make_null_session(self, app: Flask) -> NullSession: + """Creates a null session which acts as a replacement object if the + real session support could not be loaded due to a configuration + error. This mainly aids the user experience because the job of the + null session is to still support lookup without complaining but + modifications are answered with a helpful error message of what + failed. + + This creates an instance of :attr:`null_session_class` by default. + """ + return self.null_session_class() + + def is_null_session(self, obj: object) -> bool: + """Checks if a given object is a null session. Null sessions are + not asked to be saved. + + This checks if the object is an instance of :attr:`null_session_class` + by default. + """ + return isinstance(obj, self.null_session_class) + + def get_cookie_name(self, app: Flask) -> str: + """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``.""" + return app.config["SESSION_COOKIE_NAME"] # type: ignore[no-any-return] + + def get_cookie_domain(self, app: Flask) -> str | None: + """The value of the ``Domain`` parameter on the session cookie. If not set, + browsers will only send the cookie to the exact domain it was set from. + Otherwise, they will send it to any subdomain of the given value as well. + + Uses the :data:`SESSION_COOKIE_DOMAIN` config. + + .. versionchanged:: 2.3 + Not set by default, does not fall back to ``SERVER_NAME``. + """ + return app.config["SESSION_COOKIE_DOMAIN"] # type: ignore[no-any-return] + + def get_cookie_path(self, app: Flask) -> str: + """Returns the path for which the cookie should be valid. The + default implementation uses the value from the ``SESSION_COOKIE_PATH`` + config var if it's set, and falls back to ``APPLICATION_ROOT`` or + uses ``/`` if it's ``None``. + """ + return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] # type: ignore[no-any-return] + + def get_cookie_httponly(self, app: Flask) -> bool: + """Returns True if the session cookie should be httponly. This + currently just returns the value of the ``SESSION_COOKIE_HTTPONLY`` + config var. + """ + return app.config["SESSION_COOKIE_HTTPONLY"] # type: ignore[no-any-return] + + def get_cookie_secure(self, app: Flask) -> bool: + """Returns True if the cookie should be secure. This currently + just returns the value of the ``SESSION_COOKIE_SECURE`` setting. + """ + return app.config["SESSION_COOKIE_SECURE"] # type: ignore[no-any-return] + + def get_cookie_samesite(self, app: Flask) -> str | None: + """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the + ``SameSite`` attribute. This currently just returns the value of + the :data:`SESSION_COOKIE_SAMESITE` setting. + """ + return app.config["SESSION_COOKIE_SAMESITE"] # type: ignore[no-any-return] + + def get_cookie_partitioned(self, app: Flask) -> bool: + """Returns True if the cookie should be partitioned. By default, uses + the value of :data:`SESSION_COOKIE_PARTITIONED`. + + .. versionadded:: 3.1 + """ + return app.config["SESSION_COOKIE_PARTITIONED"] # type: ignore[no-any-return] + + def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None: + """A helper method that returns an expiration date for the session + or ``None`` if the session is linked to the browser session. The + default implementation returns now + the permanent session + lifetime configured on the application. + """ + if session.permanent: + return datetime.now(timezone.utc) + app.permanent_session_lifetime + return None + + def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool: + """Used by session backends to determine if a ``Set-Cookie`` header + should be set for this session cookie for this response. If the session + has been modified, the cookie is set. If the session is permanent and + the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is + always set. + + This check is usually skipped if the session was deleted. + + .. versionadded:: 0.11 + """ + + return session.modified or ( + session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"] + ) + + def open_session(self, app: Flask, request: Request) -> SessionMixin | None: + """This is called at the beginning of each request, after + pushing the request context, before matching the URL. + + This must return an object which implements a dictionary-like + interface as well as the :class:`SessionMixin` interface. + + This will return ``None`` to indicate that loading failed in + some way that is not immediately an error. The request + context will fall back to using :meth:`make_null_session` + in this case. + """ + raise NotImplementedError() + + def save_session( + self, app: Flask, session: SessionMixin, response: Response + ) -> None: + """This is called at the end of each request, after generating + a response, before removing the request context. It is skipped + if :meth:`is_null_session` returns ``True``. + """ + raise NotImplementedError() + + +session_json_serializer = TaggedJSONSerializer() + + +def _lazy_sha1(string: bytes = b"") -> t.Any: + """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include + SHA-1, in which case the import and use as a default would fail before the + developer can configure something else. + """ + return hashlib.sha1(string) + + +class SecureCookieSessionInterface(SessionInterface): + """The default session interface that stores sessions in signed cookies + through the :mod:`itsdangerous` module. + """ + + #: the salt that should be applied on top of the secret key for the + #: signing of cookie based sessions. + salt = "cookie-session" + #: the hash function to use for the signature. The default is sha1 + digest_method = staticmethod(_lazy_sha1) + #: the name of the itsdangerous supported key derivation. The default + #: is hmac. + key_derivation = "hmac" + #: A python serializer for the payload. The default is a compact + #: JSON derived serializer with support for some extra Python types + #: such as datetime objects or tuples. + serializer = session_json_serializer + session_class = SecureCookieSession + + def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None: + if not app.secret_key: + return None + + keys: list[str | bytes] = [] + + if fallbacks := app.config["SECRET_KEY_FALLBACKS"]: + keys.extend(fallbacks) + + keys.append(app.secret_key) # itsdangerous expects current key at top + return URLSafeTimedSerializer( + keys, # type: ignore[arg-type] + salt=self.salt, + serializer=self.serializer, + signer_kwargs={ + "key_derivation": self.key_derivation, + "digest_method": self.digest_method, + }, + ) + + def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None: + s = self.get_signing_serializer(app) + if s is None: + return None + val = request.cookies.get(self.get_cookie_name(app)) + if not val: + return self.session_class() + max_age = int(app.permanent_session_lifetime.total_seconds()) + try: + data = s.loads(val, max_age=max_age) + return self.session_class(data) + except BadSignature: + return self.session_class() + + def save_session( + self, app: Flask, session: SessionMixin, response: Response + ) -> None: + name = self.get_cookie_name(app) + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + secure = self.get_cookie_secure(app) + partitioned = self.get_cookie_partitioned(app) + samesite = self.get_cookie_samesite(app) + httponly = self.get_cookie_httponly(app) + + # Add a "Vary: Cookie" header if the session was accessed at all. + if session.accessed: + response.vary.add("Cookie") + + # If the session is modified to be empty, remove the cookie. + # If the session is empty, return without setting the cookie. + if not session: + if session.modified: + response.delete_cookie( + name, + domain=domain, + path=path, + secure=secure, + partitioned=partitioned, + samesite=samesite, + httponly=httponly, + ) + response.vary.add("Cookie") + + return + + if not self.should_set_cookie(app, session): + return + + expires = self.get_expiration_time(app, session) + val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore[union-attr] + response.set_cookie( + name, + val, + expires=expires, + httponly=httponly, + domain=domain, + path=path, + secure=secure, + partitioned=partitioned, + samesite=samesite, + ) + response.vary.add("Cookie") diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/signals.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/signals.py new file mode 100644 index 0000000..444fda9 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/signals.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from blinker import Namespace + +# This namespace is only for signals provided by Flask itself. +_signals = Namespace() + +template_rendered = _signals.signal("template-rendered") +before_render_template = _signals.signal("before-render-template") +request_started = _signals.signal("request-started") +request_finished = _signals.signal("request-finished") +request_tearing_down = _signals.signal("request-tearing-down") +got_request_exception = _signals.signal("got-request-exception") +appcontext_tearing_down = _signals.signal("appcontext-tearing-down") +appcontext_pushed = _signals.signal("appcontext-pushed") +appcontext_popped = _signals.signal("appcontext-popped") +message_flashed = _signals.signal("message-flashed") diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/templating.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/templating.py new file mode 100644 index 0000000..16d480f --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/templating.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import typing as t + +from jinja2 import BaseLoader +from jinja2 import Environment as BaseEnvironment +from jinja2 import Template +from jinja2 import TemplateNotFound + +from .globals import _cv_app +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .helpers import stream_with_context +from .signals import before_render_template +from .signals import template_rendered + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .sansio.app import App + from .sansio.scaffold import Scaffold + + +def _default_template_ctx_processor() -> dict[str, t.Any]: + """Default template context processor. Injects `request`, + `session` and `g`. + """ + appctx = _cv_app.get(None) + reqctx = _cv_request.get(None) + rv: dict[str, t.Any] = {} + if appctx is not None: + rv["g"] = appctx.g + if reqctx is not None: + rv["request"] = reqctx.request + rv["session"] = reqctx.session + return rv + + +class Environment(BaseEnvironment): + """Works like a regular Jinja environment but has some additional + knowledge of how Flask's blueprint works so that it can prepend the + name of the blueprint to referenced templates if necessary. + """ + + def __init__(self, app: App, **options: t.Any) -> None: + if "loader" not in options: + options["loader"] = app.create_global_jinja_loader() + BaseEnvironment.__init__(self, **options) + self.app = app + + +class DispatchingJinjaLoader(BaseLoader): + """A loader that looks for templates in the application and all + the blueprint folders. + """ + + def __init__(self, app: App) -> None: + self.app = app + + def get_source( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + if self.app.config["EXPLAIN_TEMPLATE_LOADING"]: + return self._get_source_explained(environment, template) + return self._get_source_fast(environment, template) + + def _get_source_explained( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + attempts = [] + rv: tuple[str, str | None, t.Callable[[], bool] | None] | None + trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None + + for srcobj, loader in self._iter_loaders(template): + try: + rv = loader.get_source(environment, template) + if trv is None: + trv = rv + except TemplateNotFound: + rv = None + attempts.append((loader, srcobj, rv)) + + from .debughelpers import explain_template_loading_attempts + + explain_template_loading_attempts(self.app, template, attempts) + + if trv is not None: + return trv + raise TemplateNotFound(template) + + def _get_source_fast( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + for _srcobj, loader in self._iter_loaders(template): + try: + return loader.get_source(environment, template) + except TemplateNotFound: + continue + raise TemplateNotFound(template) + + def _iter_loaders(self, template: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]: + loader = self.app.jinja_loader + if loader is not None: + yield self.app, loader + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + yield blueprint, loader + + def list_templates(self) -> list[str]: + result = set() + loader = self.app.jinja_loader + if loader is not None: + result.update(loader.list_templates()) + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + for template in loader.list_templates(): + result.add(template) + + return list(result) + + +def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + rv = template.render(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + return rv + + +def render_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> str: + """Render a template by name with the given context. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _render(app, template, context) + + +def render_template_string(source: str, **context: t.Any) -> str: + """Render a template from the given source string with the given + context. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _render(app, template, context) + + +def _stream( + app: Flask, template: Template, context: dict[str, t.Any] +) -> t.Iterator[str]: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + def generate() -> t.Iterator[str]: + yield from template.generate(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + rv = generate() + + # If a request context is active, keep it while generating. + if request: + rv = stream_with_context(rv) + + return rv + + +def stream_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> t.Iterator[str]: + """Render a template by name with the given context as a stream. + This returns an iterator of strings, which can be used as a + streaming response from a view. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _stream(app, template, context) + + +def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]: + """Render a template from the given source string with the given + context as a stream. This returns an iterator of strings, which can + be used as a streaming response from a view. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _stream(app, template, context) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/testing.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/testing.py new file mode 100644 index 0000000..55eb12f --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/testing.py @@ -0,0 +1,298 @@ +from __future__ import annotations + +import importlib.metadata +import typing as t +from contextlib import contextmanager +from contextlib import ExitStack +from copy import copy +from types import TracebackType +from urllib.parse import urlsplit + +import werkzeug.test +from click.testing import CliRunner +from click.testing import Result +from werkzeug.test import Client +from werkzeug.wrappers import Request as BaseRequest + +from .cli import ScriptInfo +from .sessions import SessionMixin + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import WSGIEnvironment + from werkzeug.test import TestResponse + + from .app import Flask + + +class EnvironBuilder(werkzeug.test.EnvironBuilder): + """An :class:`~werkzeug.test.EnvironBuilder`, that takes defaults from the + application. + + :param app: The Flask application to configure the environment from. + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + + def __init__( + self, + app: Flask, + path: str = "/", + base_url: str | None = None, + subdomain: str | None = None, + url_scheme: str | None = None, + *args: t.Any, + **kwargs: t.Any, + ) -> None: + assert not (base_url or subdomain or url_scheme) or ( + base_url is not None + ) != bool(subdomain or url_scheme), ( + 'Cannot pass "subdomain" or "url_scheme" with "base_url".' + ) + + if base_url is None: + http_host = app.config.get("SERVER_NAME") or "localhost" + app_root = app.config["APPLICATION_ROOT"] + + if subdomain: + http_host = f"{subdomain}.{http_host}" + + if url_scheme is None: + url_scheme = app.config["PREFERRED_URL_SCHEME"] + + url = urlsplit(path) + base_url = ( + f"{url.scheme or url_scheme}://{url.netloc or http_host}" + f"/{app_root.lstrip('/')}" + ) + path = url.path + + if url.query: + path = f"{path}?{url.query}" + + self.app = app + super().__init__(path, base_url, *args, **kwargs) + + def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize ``obj`` to a JSON-formatted string. + + The serialization will be configured according to the config associated + with this EnvironBuilder's ``app``. + """ + return self.app.json.dumps(obj, **kwargs) + + +_werkzeug_version = "" + + +def _get_werkzeug_version() -> str: + global _werkzeug_version + + if not _werkzeug_version: + _werkzeug_version = importlib.metadata.version("werkzeug") + + return _werkzeug_version + + +class FlaskClient(Client): + """Works like a regular Werkzeug test client but has knowledge about + Flask's contexts to defer the cleanup of the request context until + the end of a ``with`` block. For general information about how to + use this class refer to :class:`werkzeug.test.Client`. + + .. versionchanged:: 0.12 + `app.test_client()` includes preset default environment, which can be + set after instantiation of the `app.test_client()` object in + `client.environ_base`. + + Basic usage is outlined in the :doc:`/testing` chapter. + """ + + application: Flask + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + super().__init__(*args, **kwargs) + self.preserve_context = False + self._new_contexts: list[t.ContextManager[t.Any]] = [] + self._context_stack = ExitStack() + self.environ_base = { + "REMOTE_ADDR": "127.0.0.1", + "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}", + } + + @contextmanager + def session_transaction( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Iterator[SessionMixin]: + """When used in combination with a ``with`` statement this opens a + session transaction. This can be used to modify the session that + the test client uses. Once the ``with`` block is left the session is + stored back. + + :: + + with client.session_transaction() as session: + session['value'] = 42 + + Internally this is implemented by going through a temporary test + request context and since session handling could depend on + request variables this function accepts the same arguments as + :meth:`~flask.Flask.test_request_context` which are directly + passed through. + """ + if self._cookies is None: + raise TypeError( + "Cookies are disabled. Create a client with 'use_cookies=True'." + ) + + app = self.application + ctx = app.test_request_context(*args, **kwargs) + self._add_cookies_to_wsgi(ctx.request.environ) + + with ctx: + sess = app.session_interface.open_session(app, ctx.request) + + if sess is None: + raise RuntimeError("Session backend did not open a session.") + + yield sess + resp = app.response_class() + + if app.session_interface.is_null_session(sess): + return + + with ctx: + app.session_interface.save_session(app, sess, resp) + + self._update_cookies_from_response( + ctx.request.host.partition(":")[0], + ctx.request.path, + resp.headers.getlist("Set-Cookie"), + ) + + def _copy_environ(self, other: WSGIEnvironment) -> WSGIEnvironment: + out = {**self.environ_base, **other} + + if self.preserve_context: + out["werkzeug.debug.preserve_context"] = self._new_contexts.append + + return out + + def _request_from_builder_args( + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] + ) -> BaseRequest: + kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {})) + builder = EnvironBuilder(self.application, *args, **kwargs) + + try: + return builder.get_request() + finally: + builder.close() + + def open( + self, + *args: t.Any, + buffered: bool = False, + follow_redirects: bool = False, + **kwargs: t.Any, + ) -> TestResponse: + if args and isinstance( + args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest) + ): + if isinstance(args[0], werkzeug.test.EnvironBuilder): + builder = copy(args[0]) + builder.environ_base = self._copy_environ(builder.environ_base or {}) # type: ignore[arg-type] + request = builder.get_request() + elif isinstance(args[0], dict): + request = EnvironBuilder.from_environ( + args[0], app=self.application, environ_base=self._copy_environ({}) + ).get_request() + else: + # isinstance(args[0], BaseRequest) + request = copy(args[0]) + request.environ = self._copy_environ(request.environ) + else: + # request is None + request = self._request_from_builder_args(args, kwargs) + + # Pop any previously preserved contexts. This prevents contexts + # from being preserved across redirects or multiple requests + # within a single block. + self._context_stack.close() + + response = super().open( + request, + buffered=buffered, + follow_redirects=follow_redirects, + ) + response.json_module = self.application.json # type: ignore[assignment] + + # Re-push contexts that were preserved during the request. + for cm in self._new_contexts: + self._context_stack.enter_context(cm) + + self._new_contexts.clear() + return response + + def __enter__(self) -> FlaskClient: + if self.preserve_context: + raise RuntimeError("Cannot nest client invocations") + self.preserve_context = True + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.preserve_context = False + self._context_stack.close() + + +class FlaskCliRunner(CliRunner): + """A :class:`~click.testing.CliRunner` for testing a Flask app's + CLI commands. Typically created using + :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`. + """ + + def __init__(self, app: Flask, **kwargs: t.Any) -> None: + self.app = app + super().__init__(**kwargs) + + def invoke( # type: ignore + self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any + ) -> Result: + """Invokes a CLI command in an isolated environment. See + :meth:`CliRunner.invoke ` for + full method documentation. See :ref:`testing-cli` for examples. + + If the ``obj`` argument is not given, passes an instance of + :class:`~flask.cli.ScriptInfo` that knows how to load the Flask + app being tested. + + :param cli: Command object to invoke. Default is the app's + :attr:`~flask.app.Flask.cli` group. + :param args: List of strings to invoke the command with. + + :return: a :class:`~click.testing.Result` object. + """ + if cli is None: + cli = self.app.cli + + if "obj" not in kwargs: + kwargs["obj"] = ScriptInfo(create_app=lambda: self.app) + + return super().invoke(cli, args, **kwargs) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/typing.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/typing.py new file mode 100644 index 0000000..6b70c40 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/typing.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import collections.abc as cabc +import typing as t + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import WSGIApplication # noqa: F401 + from werkzeug.datastructures import Headers # noqa: F401 + from werkzeug.sansio.response import Response # noqa: F401 + +# The possible types that are directly convertible or are a Response object. +ResponseValue = t.Union[ + "Response", + str, + bytes, + list[t.Any], + # Only dict is actually accepted, but Mapping allows for TypedDict. + t.Mapping[str, t.Any], + t.Iterator[str], + t.Iterator[bytes], + cabc.AsyncIterable[str], # for Quart, until App is generic. + cabc.AsyncIterable[bytes], +] + +# the possible types for an individual HTTP header +# This should be a Union, but mypy doesn't pass unless it's a TypeVar. +HeaderValue = t.Union[str, list[str], tuple[str, ...]] + +# the possible types for HTTP headers +HeadersValue = t.Union[ + "Headers", + t.Mapping[str, HeaderValue], + t.Sequence[tuple[str, HeaderValue]], +] + +# The possible types returned by a route function. +ResponseReturnValue = t.Union[ + ResponseValue, + tuple[ResponseValue, HeadersValue], + tuple[ResponseValue, int], + tuple[ResponseValue, int, HeadersValue], + "WSGIApplication", +] + +# Allow any subclass of werkzeug.Response, such as the one from Flask, +# as a callback argument. Using werkzeug.Response directly makes a +# callback annotated with flask.Response fail type checking. +ResponseClass = t.TypeVar("ResponseClass", bound="Response") + +AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named +AfterRequestCallable = t.Union[ + t.Callable[[ResponseClass], ResponseClass], + t.Callable[[ResponseClass], t.Awaitable[ResponseClass]], +] +BeforeFirstRequestCallable = t.Union[ + t.Callable[[], None], t.Callable[[], t.Awaitable[None]] +] +BeforeRequestCallable = t.Union[ + t.Callable[[], t.Optional[ResponseReturnValue]], + t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]], +] +ShellContextProcessorCallable = t.Callable[[], dict[str, t.Any]] +TeardownCallable = t.Union[ + t.Callable[[t.Optional[BaseException]], None], + t.Callable[[t.Optional[BaseException]], t.Awaitable[None]], +] +TemplateContextProcessorCallable = t.Union[ + t.Callable[[], dict[str, t.Any]], + t.Callable[[], t.Awaitable[dict[str, t.Any]]], +] +TemplateFilterCallable = t.Callable[..., t.Any] +TemplateGlobalCallable = t.Callable[..., t.Any] +TemplateTestCallable = t.Callable[..., bool] +URLDefaultCallable = t.Callable[[str, dict[str, t.Any]], None] +URLValuePreprocessorCallable = t.Callable[ + [t.Optional[str], t.Optional[dict[str, t.Any]]], None +] + +# This should take Exception, but that either breaks typing the argument +# with a specific exception, or decorating multiple times with different +# exceptions (and using a union type on the argument). +# https://github.com/pallets/flask/issues/4095 +# https://github.com/pallets/flask/issues/4295 +# https://github.com/pallets/flask/issues/4297 +ErrorHandlerCallable = t.Union[ + t.Callable[[t.Any], ResponseReturnValue], + t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]], +] + +RouteCallable = t.Union[ + t.Callable[..., ResponseReturnValue], + t.Callable[..., t.Awaitable[ResponseReturnValue]], +] diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/views.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/views.py new file mode 100644 index 0000000..53fe976 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/views.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +import typing as t + +from . import typing as ft +from .globals import current_app +from .globals import request + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +http_method_funcs = frozenset( + ["get", "post", "head", "options", "delete", "put", "trace", "patch"] +) + + +class View: + """Subclass this class and override :meth:`dispatch_request` to + create a generic class-based view. Call :meth:`as_view` to create a + view function that creates an instance of the class with the given + arguments and calls its ``dispatch_request`` method with any URL + variables. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class Hello(View): + init_every_request = False + + def dispatch_request(self, name): + return f"Hello, {name}!" + + app.add_url_rule( + "/hello/", view_func=Hello.as_view("hello") + ) + + Set :attr:`methods` on the class to change what methods the view + accepts. + + Set :attr:`decorators` on the class to apply a list of decorators to + the generated view function. Decorators applied to the class itself + will not be applied to the generated view function! + + Set :attr:`init_every_request` to ``False`` for efficiency, unless + you need to store request-global data on ``self``. + """ + + #: The methods this view is registered for. Uses the same default + #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and + #: ``add_url_rule`` by default. + methods: t.ClassVar[t.Collection[str] | None] = None + + #: Control whether the ``OPTIONS`` method is handled automatically. + #: Uses the same default (``True``) as ``route`` and + #: ``add_url_rule`` by default. + provide_automatic_options: t.ClassVar[bool | None] = None + + #: A list of decorators to apply, in order, to the generated view + #: function. Remember that ``@decorator`` syntax is applied bottom + #: to top, so the first decorator in the list would be the bottom + #: decorator. + #: + #: .. versionadded:: 0.8 + decorators: t.ClassVar[list[t.Callable[..., t.Any]]] = [] + + #: Create a new instance of this view class for every request by + #: default. If a view subclass sets this to ``False``, the same + #: instance is used for every request. + #: + #: A single instance is more efficient, especially if complex setup + #: is done during init. However, storing data on ``self`` is no + #: longer safe across requests, and :data:`~flask.g` should be used + #: instead. + #: + #: .. versionadded:: 2.2 + init_every_request: t.ClassVar[bool] = True + + def dispatch_request(self) -> ft.ResponseReturnValue: + """The actual view function behavior. Subclasses must override + this and return a valid response. Any variables from the URL + rule are passed as keyword arguments. + """ + raise NotImplementedError() + + @classmethod + def as_view( + cls, name: str, *class_args: t.Any, **class_kwargs: t.Any + ) -> ft.RouteCallable: + """Convert the class into a view function that can be registered + for a route. + + By default, the generated view will create a new instance of the + view class for every request and call its + :meth:`dispatch_request` method. If the view class sets + :attr:`init_every_request` to ``False``, the same instance will + be used for every request. + + Except for ``name``, all other arguments passed to this method + are forwarded to the view class ``__init__`` method. + + .. versionchanged:: 2.2 + Added the ``init_every_request`` class attribute. + """ + if cls.init_every_request: + + def view(**kwargs: t.Any) -> ft.ResponseReturnValue: + self = view.view_class( # type: ignore[attr-defined] + *class_args, **class_kwargs + ) + return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return] + + else: + self = cls(*class_args, **class_kwargs) # pyright: ignore + + def view(**kwargs: t.Any) -> ft.ResponseReturnValue: + return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return] + + if cls.decorators: + view.__name__ = name + view.__module__ = cls.__module__ + for decorator in cls.decorators: + view = decorator(view) + + # We attach the view class to the view function for two reasons: + # first of all it allows us to easily figure out what class-based + # view this thing came from, secondly it's also used for instantiating + # the view class so you can actually replace it with something else + # for testing purposes and debugging. + view.view_class = cls # type: ignore + view.__name__ = name + view.__doc__ = cls.__doc__ + view.__module__ = cls.__module__ + view.methods = cls.methods # type: ignore + view.provide_automatic_options = cls.provide_automatic_options # type: ignore + return view + + +class MethodView(View): + """Dispatches request methods to the corresponding instance methods. + For example, if you implement a ``get`` method, it will be used to + handle ``GET`` requests. + + This can be useful for defining a REST API. + + :attr:`methods` is automatically set based on the methods defined on + the class. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class CounterAPI(MethodView): + def get(self): + return str(session.get("counter", 0)) + + def post(self): + session["counter"] = session.get("counter", 0) + 1 + return redirect(url_for("counter")) + + app.add_url_rule( + "/counter", view_func=CounterAPI.as_view("counter") + ) + """ + + def __init_subclass__(cls, **kwargs: t.Any) -> None: + super().__init_subclass__(**kwargs) + + if "methods" not in cls.__dict__: + methods = set() + + for base in cls.__bases__: + if getattr(base, "methods", None): + methods.update(base.methods) # type: ignore[attr-defined] + + for key in http_method_funcs: + if hasattr(cls, key): + methods.add(key.upper()) + + if methods: + cls.methods = methods + + def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue: + meth = getattr(self, request.method.lower(), None) + + # If the request method is HEAD and we don't have a handler for it + # retry with GET. + if meth is None and request.method == "HEAD": + meth = getattr(self, "get", None) + + assert meth is not None, f"Unimplemented method {request.method!r}" + return current_app.ensure_sync(meth)(**kwargs) # type: ignore[no-any-return] diff --git a/web_viewer/.venv/lib/python3.12/site-packages/flask/wrappers.py b/web_viewer/.venv/lib/python3.12/site-packages/flask/wrappers.py new file mode 100644 index 0000000..bab6102 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/flask/wrappers.py @@ -0,0 +1,257 @@ +from __future__ import annotations + +import typing as t + +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import HTTPException +from werkzeug.wrappers import Request as RequestBase +from werkzeug.wrappers import Response as ResponseBase + +from . import json +from .globals import current_app +from .helpers import _split_blueprint_path + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.routing import Rule + + +class Request(RequestBase): + """The request object used by default in Flask. Remembers the + matched endpoint and view arguments. + + It is what ends up as :class:`~flask.request`. If you want to replace + the request object used you can subclass this and set + :attr:`~flask.Flask.request_class` to your subclass. + + The request object is a :class:`~werkzeug.wrappers.Request` subclass and + provides all of the attributes Werkzeug defines plus a few Flask + specific ones. + """ + + json_module: t.Any = json + + #: The internal URL rule that matched the request. This can be + #: useful to inspect which methods are allowed for the URL from + #: a before/after handler (``request.url_rule.methods``) etc. + #: Though if the request's method was invalid for the URL rule, + #: the valid list is available in ``routing_exception.valid_methods`` + #: instead (an attribute of the Werkzeug exception + #: :exc:`~werkzeug.exceptions.MethodNotAllowed`) + #: because the request was never internally bound. + #: + #: .. versionadded:: 0.6 + url_rule: Rule | None = None + + #: A dict of view arguments that matched the request. If an exception + #: happened when matching, this will be ``None``. + view_args: dict[str, t.Any] | None = None + + #: If matching the URL failed, this is the exception that will be + #: raised / was raised as part of the request handling. This is + #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or + #: something similar. + routing_exception: HTTPException | None = None + + _max_content_length: int | None = None + _max_form_memory_size: int | None = None + _max_form_parts: int | None = None + + @property + def max_content_length(self) -> int | None: + """The maximum number of bytes that will be read during this request. If + this limit is exceeded, a 413 :exc:`~werkzeug.exceptions.RequestEntityTooLarge` + error is raised. If it is set to ``None``, no limit is enforced at the + Flask application level. However, if it is ``None`` and the request has + no ``Content-Length`` header and the WSGI server does not indicate that + it terminates the stream, then no data is read to avoid an infinite + stream. + + Each request defaults to the :data:`MAX_CONTENT_LENGTH` config, which + defaults to ``None``. It can be set on a specific ``request`` to apply + the limit to that specific view. This should be set appropriately based + on an application's or view's specific needs. + + .. versionchanged:: 3.1 + This can be set per-request. + + .. versionchanged:: 0.6 + This is configurable through Flask config. + """ + if self._max_content_length is not None: + return self._max_content_length + + if not current_app: + return super().max_content_length + + return current_app.config["MAX_CONTENT_LENGTH"] # type: ignore[no-any-return] + + @max_content_length.setter + def max_content_length(self, value: int | None) -> None: + self._max_content_length = value + + @property + def max_form_memory_size(self) -> int | None: + """The maximum size in bytes any non-file form field may be in a + ``multipart/form-data`` body. If this limit is exceeded, a 413 + :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it + is set to ``None``, no limit is enforced at the Flask application level. + + Each request defaults to the :data:`MAX_FORM_MEMORY_SIZE` config, which + defaults to ``500_000``. It can be set on a specific ``request`` to + apply the limit to that specific view. This should be set appropriately + based on an application's or view's specific needs. + + .. versionchanged:: 3.1 + This is configurable through Flask config. + """ + if self._max_form_memory_size is not None: + return self._max_form_memory_size + + if not current_app: + return super().max_form_memory_size + + return current_app.config["MAX_FORM_MEMORY_SIZE"] # type: ignore[no-any-return] + + @max_form_memory_size.setter + def max_form_memory_size(self, value: int | None) -> None: + self._max_form_memory_size = value + + @property # type: ignore[override] + def max_form_parts(self) -> int | None: + """The maximum number of fields that may be present in a + ``multipart/form-data`` body. If this limit is exceeded, a 413 + :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it + is set to ``None``, no limit is enforced at the Flask application level. + + Each request defaults to the :data:`MAX_FORM_PARTS` config, which + defaults to ``1_000``. It can be set on a specific ``request`` to apply + the limit to that specific view. This should be set appropriately based + on an application's or view's specific needs. + + .. versionchanged:: 3.1 + This is configurable through Flask config. + """ + if self._max_form_parts is not None: + return self._max_form_parts + + if not current_app: + return super().max_form_parts + + return current_app.config["MAX_FORM_PARTS"] # type: ignore[no-any-return] + + @max_form_parts.setter + def max_form_parts(self, value: int | None) -> None: + self._max_form_parts = value + + @property + def endpoint(self) -> str | None: + """The endpoint that matched the request URL. + + This will be ``None`` if matching failed or has not been + performed yet. + + This in combination with :attr:`view_args` can be used to + reconstruct the same URL or a modified URL. + """ + if self.url_rule is not None: + return self.url_rule.endpoint # type: ignore[no-any-return] + + return None + + @property + def blueprint(self) -> str | None: + """The registered name of the current blueprint. + + This will be ``None`` if the endpoint is not part of a + blueprint, or if URL matching failed or has not been performed + yet. + + This does not necessarily match the name the blueprint was + created with. It may have been nested, or registered with a + different name. + """ + endpoint = self.endpoint + + if endpoint is not None and "." in endpoint: + return endpoint.rpartition(".")[0] + + return None + + @property + def blueprints(self) -> list[str]: + """The registered names of the current blueprint upwards through + parent blueprints. + + This will be an empty list if there is no current blueprint, or + if URL matching failed. + + .. versionadded:: 2.0.1 + """ + name = self.blueprint + + if name is None: + return [] + + return _split_blueprint_path(name) + + def _load_form_data(self) -> None: + super()._load_form_data() + + # In debug mode we're replacing the files multidict with an ad-hoc + # subclass that raises a different error for key errors. + if ( + current_app + and current_app.debug + and self.mimetype != "multipart/form-data" + and not self.files + ): + from .debughelpers import attach_enctype_error_multidict + + attach_enctype_error_multidict(self) + + def on_json_loading_failed(self, e: ValueError | None) -> t.Any: + try: + return super().on_json_loading_failed(e) + except BadRequest as ebr: + if current_app and current_app.debug: + raise + + raise BadRequest() from ebr + + +class Response(ResponseBase): + """The response object that is used by default in Flask. Works like the + response object from Werkzeug but is set to have an HTML mimetype by + default. Quite often you don't have to create this object yourself because + :meth:`~flask.Flask.make_response` will take care of that for you. + + If you want to replace the response object used you can subclass this and + set :attr:`~flask.Flask.response_class` to your subclass. + + .. versionchanged:: 1.0 + JSON support is added to the response, like the request. This is useful + when testing to get the test client response data as JSON. + + .. versionchanged:: 1.0 + + Added :attr:`max_cookie_size`. + """ + + default_mimetype: str | None = "text/html" + + json_module = json + + autocorrect_location_header = False + + @property + def max_cookie_size(self) -> int: # type: ignore + """Read-only view of the :data:`MAX_COOKIE_SIZE` config key. + + See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in + Werkzeug's docs. + """ + if current_app: + return current_app.config["MAX_COOKIE_SIZE"] # type: ignore[no-any-return] + + # return Werkzeug's default when not in an app context + return super().max_cookie_size diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt new file mode 100644 index 0000000..7b190ca --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2011 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/METADATA b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/METADATA new file mode 100644 index 0000000..ddf5464 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/METADATA @@ -0,0 +1,60 @@ +Metadata-Version: 2.1 +Name: itsdangerous +Version: 2.2.0 +Summary: Safely pass data to untrusted environments and back. +Maintainer-email: Pallets +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://itsdangerous.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/itsdangerous/ + +# ItsDangerous + +... so better sign this + +Various helpers to pass data to untrusted environments and to get it +back safe and sound. Data is cryptographically signed to ensure that a +token has not been tampered with. + +It's possible to customize how data is serialized. Data is compressed as +needed. A timestamp can be added and verified automatically while +loading a token. + + +## A Simple Example + +Here's how you could generate a token for transmitting a user's id and +name between web requests. + +```python +from itsdangerous import URLSafeSerializer +auth_s = URLSafeSerializer("secret key", "auth") +token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) + +print(token) +# eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg + +data = auth_s.loads(token) +print(data["name"]) +# itsdangerous +``` + + +## Donate + +The Pallets organization develops and supports ItsDangerous and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +[please donate today][]. + +[please donate today]: https://palletsprojects.com/donate + diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/RECORD b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/RECORD new file mode 100644 index 0000000..1394876 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/RECORD @@ -0,0 +1,22 @@ +itsdangerous-2.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +itsdangerous-2.2.0.dist-info/LICENSE.txt,sha256=Y68JiRtr6K0aQlLtQ68PTvun_JSOIoNnvtfzxa4LCdc,1475 +itsdangerous-2.2.0.dist-info/METADATA,sha256=0rk0-1ZwihuU5DnwJVwPWoEI4yWOyCexih3JyZHblhE,1924 +itsdangerous-2.2.0.dist-info/RECORD,, +itsdangerous-2.2.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +itsdangerous/__init__.py,sha256=4SK75sCe29xbRgQE1ZQtMHnKUuZYAf3bSpZOrff1IAY,1427 +itsdangerous/__pycache__/__init__.cpython-312.pyc,, +itsdangerous/__pycache__/_json.cpython-312.pyc,, +itsdangerous/__pycache__/encoding.cpython-312.pyc,, +itsdangerous/__pycache__/exc.cpython-312.pyc,, +itsdangerous/__pycache__/serializer.cpython-312.pyc,, +itsdangerous/__pycache__/signer.cpython-312.pyc,, +itsdangerous/__pycache__/timed.cpython-312.pyc,, +itsdangerous/__pycache__/url_safe.cpython-312.pyc,, +itsdangerous/_json.py,sha256=wPQGmge2yZ9328EHKF6gadGeyGYCJQKxtU-iLKE6UnA,473 +itsdangerous/encoding.py,sha256=wwTz5q_3zLcaAdunk6_vSoStwGqYWe307Zl_U87aRFM,1409 +itsdangerous/exc.py,sha256=Rr3exo0MRFEcPZltwecyK16VV1bE2K9_F1-d-ljcUn4,3201 +itsdangerous/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +itsdangerous/serializer.py,sha256=PmdwADLqkSyQLZ0jOKAgDsAW4k_H0TlA71Ei3z0C5aI,15601 +itsdangerous/signer.py,sha256=YO0CV7NBvHA6j549REHJFUjUojw2pHqwcUpQnU7yNYQ,9647 +itsdangerous/timed.py,sha256=6RvDMqNumGMxf0-HlpaZdN9PUQQmRvrQGplKhxuivUs,8083 +itsdangerous/url_safe.py,sha256=az4e5fXi_vs-YbWj8YZwn4wiVKfeD--GEKRT5Ueu4P4,2505 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/WHEEL b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/WHEEL new file mode 100644 index 0000000..3b5e64b --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous-2.2.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__init__.py b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__init__.py new file mode 100644 index 0000000..ea55256 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__init__.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import typing as t + +from .encoding import base64_decode as base64_decode +from .encoding import base64_encode as base64_encode +from .encoding import want_bytes as want_bytes +from .exc import BadData as BadData +from .exc import BadHeader as BadHeader +from .exc import BadPayload as BadPayload +from .exc import BadSignature as BadSignature +from .exc import BadTimeSignature as BadTimeSignature +from .exc import SignatureExpired as SignatureExpired +from .serializer import Serializer as Serializer +from .signer import HMACAlgorithm as HMACAlgorithm +from .signer import NoneAlgorithm as NoneAlgorithm +from .signer import Signer as Signer +from .timed import TimedSerializer as TimedSerializer +from .timed import TimestampSigner as TimestampSigner +from .url_safe import URLSafeSerializer as URLSafeSerializer +from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer + + +def __getattr__(name: str) -> t.Any: + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " ItsDangerous 2.3. Use feature detection or" + " 'importlib.metadata.version(\"itsdangerous\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("itsdangerous") + + raise AttributeError(name) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b70e2c8f4ef41e32bf4145334b31b45b2569c66 GIT binary patch literal 1640 zcmZXU&u<$=6vt=%W4&H`ZO8dtqIMGs=^pBZq##vPNTDed2_i&VA?;-~_Dq~f_lGq* zj_W9q5aIwQ4jkGmH&FRcxU}@pYAZ`fEr%cuk&;tSyqVaMQ%0+|pZDF_w{PaX**`4H zL@@gQSnTIKLNBE=8ktkjj{gOCfCwU5fI`jFG|8DD<7G*f2SuIQCtU}^=1`M26N%OH?MdK_=0x2raooqXF%N zvIuF%4*?#aeQ2M1R}kVKkqkirI?&WzJIL&3cI4bQ2FVJ2B$3?j`9}~-4h`*VJH{!G z+nv(5cgjS6G|Xrd32XFxAC1)SG^enUmVAGg@&sz^`&>5t-Y<8ysl7ZVFWXoM&YE38 zZI;-Cb~tTeL5YncV)s}O*iCA48pgYzvB>^hB%IuX!6jYFBItUeU0buXa1G)T+i zlbu3Z|9{}r=wTkQsGX!aNojq3xD-g{o6|?BF-jvX4D(1UYoq1g;ymU~HqAviq-pW& z6MO*@N@R^}+kVa5?Kq?^?ooF=ZgoQ%iNxiwHtuinI1)4>ZjUzoT}FG9yRhD9*M%K$ zJAJVoN2`tZSKNdNdbIX){oXvCS!f3o#@EA^n7;isDz3$CrB@vM-^0?e^y<5QeAvj zb)Hn6UmH)W*B=|#Uzu|cr=FS@ALlL}Ls;bG_!8XGY$AB7a|#DG+RKZ^TGV$IQq%Xh zy7CM|I&wQwUGzJ!~CIBq3Hbmjwz6 z8XGO4w6MVNr`SL=QCCe!u+a*kFuBS%yT_p>PO@))^X9#I@BQA}w*v#c2v+)IAozq4 zI#7p`WfTA(-vqFUD5AK5nz(|o!dgSC==p9`ObtCkRNp|<*wG5Fij``v`CT1cob{0~ zj3kjk6pC`SuqdQnY$rh&s$hyL8pRcz8PufOhOyJJub7~*Ak3-8N)5Ls>-X zb2OszZ|d@<6dc*h09(enD^pTE=J)@>Bh7tAn|LE_!3!Q!V9FJ zBwS!nt?=^Tr8wm_L|Ym8lbBMTg=)7L2C z63%=gszLBD>lG{}oXCg^S8L;`HooKPN-HU=k)$Dc4?L5ZG-<|Qk?GNVJ+qctgf9r8 zTIe`jvA{<_{6kUhGYEQd3&c9ww+BDiV|(`4R%!e4d;9vw?s;mv_q{#yr9s6lN{6IFktv+omkt3scwPXTgM@S7Nk_&H>7FaD&OV zeLo~k=KEQ%?-w%+xZn4mC8W{0>G6FURh3$(KdjnhFeo%1fV{*vD9@;MqhGA#30{36_+CegNMi&F0Lakx-3B`$zC)G@|i(f9!x{ftI_S$%l&2!Y5we*+mO B8Ug?S literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8266ee52457b3cac42e18b3e45908e2818eb3e13 GIT binary patch literal 2694 zcma(SOKclObjG{hINogHB;->duuV#XTM|PXLMWxs(nbZ*AShp>Vm6*hx@NNuGvlN& z2?~eN)?B4ibxNrTzE6v2Jf;}{YvlvS{=Ebrxg|)1vu%6Wwj%N*` z5#4K=Nn{fkH6zm63~>8L+DQDPc($3Oi1Cr0O#*HLY*MYz8wuaWUm{q)dwU*HUm}wD zN-Ok5^KDF;FM!5!WZPnh>IGX(8URUd?P;g&g2KKTELq7aINvW`VmfDqn37V%;%+Oq^FPlC9Kv=$^??)qFfex3#z7c4NS zpDz*$EX;$1nz;TnEHhSQoJ+-Q+rH^$P8Vlr#+j#?qs4q_hWdhMm_r11xsrSp_!hUch1m+{=m%G4mFue`oGS8Lh*F}`ZH zEFa&5Z79FxF_=gLlsDf7qS2dCDl8!(cd1dRr++k;co9WA3r)AKhcv+v7knHmzjegn zbaccH-D*2FI2jyj7pH7xl3irBpl8Ie{qkvQGs;Vzko=J8Fq#B++uWI=wkX;qPHoq> zUwP?yKCBp<(zYkly7Ek5Oe$ePdx?4SNN#d;BorJNObVWbBr`${xIAey3t(V{=W^j* z)(vZS!N+BGgO?MiwRhF1S@$oGUoqR)&4H>paDKdI?p-(cRn2`rjMU7riay5LV0(kJ zWg;qP{{%#%vk4}#@3aebN6VBX`nKD*U2Gx}FB=Gz;jS_$y96CXY$bRBIJb}OvnSC2 z5{ZZ}RF9Lhsn{;W+Ly49#;ZY$C}@lL!x(8=!tdjGJde(3Z=iXcPLyA$hmlZO9S}wc z!H|l||A?pUauqBU(=cv9+~IlG4GeeMFEW~rF`3q^MS^w!*Ja+7?|Igrb3B*WVSI+d z_z;^wkC;d|g+bRm>az~m-VpXs(V+R|g;{z`)^3n|)$vLp&7=cCW|usLDVJ@;zAYER z3z!Y8d*S00z*a7-^<3=z)jF~~epyd`OwRJv(a+yo@y@?k?cQB&9jxku%i|lm7C(5U zt!uq)u-Z1bX4Kj;l|<(6D=l4T%N4W#FNA^J+EwY^Uuzw?xU-UY;-3u-)^2jC)i-w? zKtHt|zFWK0+;X@}yVMm2x~vW5o@DZID8~-HSiWBjKjV0wGwIPc-&DRm^^h$}voLji zaq3h7mrvyY4; z+737iElHoaE=z*-OCSTUtWoZh_JF!UoE1u3$oEJs3)W!Jty|ecaKWSSarpp;2K9X1 z_eJ0Nch?4MJNDJg{XZP8nxhqcR9OWLF#Fcnxcrr`auml+i9tHrb4*&`@Frr~~Z?q#^l@Unq2$H$nR0%Gnxg3+B zS49-ogzS-}ya)`jFe{%wECP+5$*a0iRi}ESDvrQXm7Mda5R~!2Anr80Wvp3hlFHRP ztK-d{b|xv@crv_MCD7e@FG2WN_; zR*ZAjXVxmIr3RL*%c;KgRDU(qFUjr|e9rjHsPv3o99lmxQ9UqGOTDmcZ5kL4 L-$-Emm@?{Lj#pwF literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/exc.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/exc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da9e7516456a2fc61f16db75e0062a5ffe372292 GIT binary patch literal 3954 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 D39j9y literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc44f2f9ead5d4f114c1ac1a5f61864d7e543828 GIT binary patch literal 11300 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 literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/timed.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/timed.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f06f78093d2232ecfa5fd2b586bf7503f133d62f GIT binary patch literal 8748 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 H!}|UoDU)iM literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/url_safe.cpython-312.pyc b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/__pycache__/url_safe.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2465300328bc457cd80effb504b119be5a8aa37 GIT binary patch literal 3544 zcmbtWU2Ggz6~41Gv;Tiyd)IanZ}ac|dVRku1bRs(=(E9(Y0x5)X(zI5iThA(4Vw9?H|Ec14uFaPFO5 z@5YoO;Cg-M+;h%7bLX7%oqPTmi-i%4+TUaPA3_Md#}?fY@Cae+9U!hF4QYabNWXL6d!4EP~lZ`+rSO{|34|J#y;&i|Wmm-A-r-Mec6f49y9RfOD zh;uq@BudFbQb0a*7HN?yNQ;s<85o5#+!VbN6;fOl16iD;cBE2AI>@C7kS0Nz?CkAI zhibm;zz0kRi~HaT%z(MZwKNFRfk z9Y*-Hs=cJv3`^BOoK#+#RvmSgP+c|jI-$S|KekG|te1$^V->xVdvoWY!qzun{e5se zK?OlW1>Y+WfPzS*f~@(7UlUJ(T$42UO(BGK|7pP{S`akQj* ze2zUA_nWdUSg8&(Ra8xE!xtDeJWp`hP>bHVRSch&<7VpV-NASjj IX=FjI7>?5W zC>H8)Z&~zpg2J*whW`XY!q$FR{a64n2&-~KSZ1vCJXaU3t%jzo+lSy0WXm2KwPXu;yPB8sg7{U;1^_kbW-w zNSH+v$mwJfLI)5XTn#uKKttBZjleJbH#=Y02z=M4q1C{50#%`kE{WenRpC>22maIU zY;fkeE#`$BWoLM~e)5b}F*FQV!=1o-VB&!-d&q$p7S)PPu(g08u;fyalo>D~EOCqK zi@K>A3Zc}Z^{JjYmUfsK>r1+x!3B#FF4cGzLPB53RX?B*DnuWqeNZ1nhgA(e zmRWx78jYPgu`2bj-;laE@8j)gYQ8$uh84gql^W8jPZQxSk*?uBi*iGxvL-d;ItZas z$x!j-nddw-(g9#*{gg%4jcS!rxtq&0%~-)Mm|W2yCS_G48=&k0c13G`nT|0krrSza zk7#bVj}CYc*XTGX+H5;dZ+w|zpy_^?{%z*(+8X3_apIU{Rdxuv$%fXC;E*i-YC5rIoui?YfiWJ;pVybQ{(H> zX6ngi;IEO9R%XxI`D12 zW0{0P$wlJUtD(*_mAR7*rr+_0jQEUjWNI;tq&uBb*w z5>OP)Dk=(PNkii>xd}z-YfnJLFw5QN#yS{nrW z_&xN%C(6Ng7(M;P4JorRa%59F)sCVA2Ooq!BcHrKkZH^C1mq+EVvGu+^b>6+gFS;I z?FgfyD4h5x2Gl>E72@*n0|XPzcHj0`Bh$GCg{>rrdu2>^V30l=9Tv++ju>0fO5B zItR4ZZ_K8n6&F??J>AF|MRI!ydu`LWx`?P@CP zzyBm>?o#?Rk8(FajQ&rLax={J|D#9gGt6Hn*u?V;GwKW7=|x7cO5q6^KbvMaTc`jq z0cRAdHsI8$l_3jU!SQOGtgy?NgUt={CKIH*4dC_86`}p$T>ytaW!E%iZJ8@OtaYcX z=(-}bk{o1SNcou_Kj)+1DG%>oqNia8Lj(t7TNDK09@_sNO8o^z{)Wcyp$Y!mdk-B1 zKK!=*Cxqd(!VU2aWg7vxO$A8^Zj9t^g@1GGSBY%|tL->rjUBtCyghs8ndd)ZJnn~o E0<{I8*#H0l literal 0 HcmV?d00001 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/_json.py b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/_json.py new file mode 100644 index 0000000..fc23fea --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/_json.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import json as _json +import typing as t + + +class _CompactJSON: + """Wrapper around json module that strips whitespace.""" + + @staticmethod + def loads(payload: str | bytes) -> t.Any: + return _json.loads(payload) + + @staticmethod + def dumps(obj: t.Any, **kwargs: t.Any) -> str: + kwargs.setdefault("ensure_ascii", False) + kwargs.setdefault("separators", (",", ":")) + return _json.dumps(obj, **kwargs) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/encoding.py b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/encoding.py new file mode 100644 index 0000000..f5ca80f --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/encoding.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import base64 +import string +import struct +import typing as t + +from .exc import BadData + + +def want_bytes( + s: str | bytes, encoding: str = "utf-8", errors: str = "strict" +) -> bytes: + if isinstance(s, str): + s = s.encode(encoding, errors) + + return s + + +def base64_encode(string: str | bytes) -> bytes: + """Base64 encode a string of bytes or text. The resulting bytes are + safe to use in URLs. + """ + string = want_bytes(string) + return base64.urlsafe_b64encode(string).rstrip(b"=") + + +def base64_decode(string: str | bytes) -> bytes: + """Base64 decode a URL-safe string of bytes or text. The result is + bytes. + """ + string = want_bytes(string, encoding="ascii", errors="ignore") + string += b"=" * (-len(string) % 4) + + try: + return base64.urlsafe_b64decode(string) + except (TypeError, ValueError) as e: + raise BadData("Invalid base64-encoded data") from e + + +# The alphabet used by base64.urlsafe_* +_base64_alphabet = f"{string.ascii_letters}{string.digits}-_=".encode("ascii") + +_int64_struct = struct.Struct(">Q") +_int_to_bytes = _int64_struct.pack +_bytes_to_int = t.cast("t.Callable[[bytes], tuple[int]]", _int64_struct.unpack) + + +def int_to_bytes(num: int) -> bytes: + return _int_to_bytes(num).lstrip(b"\x00") + + +def bytes_to_int(bytestr: bytes) -> int: + return _bytes_to_int(bytestr.rjust(8, b"\x00"))[0] diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/exc.py b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/exc.py new file mode 100644 index 0000000..a75adcd --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/exc.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import typing as t +from datetime import datetime + + +class BadData(Exception): + """Raised if bad data of any sort was encountered. This is the base + for all exceptions that ItsDangerous defines. + + .. versionadded:: 0.15 + """ + + def __init__(self, message: str): + super().__init__(message) + self.message = message + + def __str__(self) -> str: + return self.message + + +class BadSignature(BadData): + """Raised if a signature does not match.""" + + def __init__(self, message: str, payload: t.Any | None = None): + super().__init__(message) + + #: The payload that failed the signature test. In some + #: situations you might still want to inspect this, even if + #: you know it was tampered with. + #: + #: .. versionadded:: 0.14 + self.payload: t.Any | None = payload + + +class BadTimeSignature(BadSignature): + """Raised if a time-based signature is invalid. This is a subclass + of :class:`BadSignature`. + """ + + def __init__( + self, + message: str, + payload: t.Any | None = None, + date_signed: datetime | None = None, + ): + super().__init__(message, payload) + + #: If the signature expired this exposes the date of when the + #: signature was created. This can be helpful in order to + #: tell the user how long a link has been gone stale. + #: + #: .. versionchanged:: 2.0 + #: The datetime value is timezone-aware rather than naive. + #: + #: .. versionadded:: 0.14 + self.date_signed = date_signed + + +class SignatureExpired(BadTimeSignature): + """Raised if a signature timestamp is older than ``max_age``. This + is a subclass of :exc:`BadTimeSignature`. + """ + + +class BadHeader(BadSignature): + """Raised if a signed header is invalid in some form. This only + happens for serializers that have a header that goes with the + signature. + + .. versionadded:: 0.24 + """ + + def __init__( + self, + message: str, + payload: t.Any | None = None, + header: t.Any | None = None, + original_error: Exception | None = None, + ): + super().__init__(message, payload) + + #: If the header is actually available but just malformed it + #: might be stored here. + self.header: t.Any | None = header + + #: If available, the error that indicates why the payload was + #: not valid. This might be ``None``. + self.original_error: Exception | None = original_error + + +class BadPayload(BadData): + """Raised if a payload is invalid. This could happen if the payload + is loaded despite an invalid signature, or if there is a mismatch + between the serializer and deserializer. The original exception + that occurred during loading is stored on as :attr:`original_error`. + + .. versionadded:: 0.15 + """ + + def __init__(self, message: str, original_error: Exception | None = None): + super().__init__(message) + + #: If available, the error that indicates why the payload was + #: not valid. This might be ``None``. + self.original_error: Exception | None = original_error diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/py.typed b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/serializer.py b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/serializer.py new file mode 100644 index 0000000..5ddf387 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/serializer.py @@ -0,0 +1,406 @@ +from __future__ import annotations + +import collections.abc as cabc +import json +import typing as t + +from .encoding import want_bytes +from .exc import BadPayload +from .exc import BadSignature +from .signer import _make_keys_list +from .signer import Signer + +if t.TYPE_CHECKING: + import typing_extensions as te + + # This should be either be str or bytes. To avoid having to specify the + # bound type, it falls back to a union if structural matching fails. + _TSerialized = te.TypeVar( + "_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes] + ) +else: + # Still available at runtime on Python < 3.13, but without the default. + _TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes]) + + +class _PDataSerializer(t.Protocol[_TSerialized]): + def loads(self, payload: _TSerialized, /) -> t.Any: ... + # A signature with additional arguments is not handled correctly by type + # checkers right now, so an overload is used below for serializers that + # don't match this strict protocol. + def dumps(self, obj: t.Any, /) -> _TSerialized: ... + + +# Use TypeIs once it's available in typing_extensions or 3.13. +def is_text_serializer( + serializer: _PDataSerializer[t.Any], +) -> te.TypeGuard[_PDataSerializer[str]]: + """Checks whether a serializer generates text or binary.""" + return isinstance(serializer.dumps({}), str) + + +class Serializer(t.Generic[_TSerialized]): + """A serializer wraps a :class:`~itsdangerous.signer.Signer` to + enable serializing and securely signing data other than bytes. It + can unsign to verify that the data hasn't been changed. + + The serializer provides :meth:`dumps` and :meth:`loads`, similar to + :mod:`json`, and by default uses :mod:`json` internally to serialize + the data to bytes. + + The secret key should be a random string of ``bytes`` and should not + be saved to code or version control. Different salts should be used + to distinguish signing in different contexts. See :doc:`/concepts` + for information about the security of the secret key and salt. + + :param secret_key: The secret key to sign and verify with. Can be a + list of keys, oldest to newest, to support key rotation. + :param salt: Extra key to combine with ``secret_key`` to distinguish + signatures in different contexts. + :param serializer: An object that provides ``dumps`` and ``loads`` + methods for serializing data to a string. Defaults to + :attr:`default_serializer`, which defaults to :mod:`json`. + :param serializer_kwargs: Keyword arguments to pass when calling + ``serializer.dumps``. + :param signer: A ``Signer`` class to instantiate when signing data. + Defaults to :attr:`default_signer`, which defaults to + :class:`~itsdangerous.signer.Signer`. + :param signer_kwargs: Keyword arguments to pass when instantiating + the ``Signer`` class. + :param fallback_signers: List of signer parameters to try when + unsigning with the default signer fails. Each item can be a dict + of ``signer_kwargs``, a ``Signer`` class, or a tuple of + ``(signer, signer_kwargs)``. Defaults to + :attr:`default_fallback_signers`. + + .. versionchanged:: 2.0 + Added support for key rotation by passing a list to + ``secret_key``. + + .. versionchanged:: 2.0 + Removed the default SHA-512 fallback signer from + ``default_fallback_signers``. + + .. versionchanged:: 1.1 + Added support for ``fallback_signers`` and configured a default + SHA-512 fallback. This fallback is for users who used the yanked + 1.0.0 release which defaulted to SHA-512. + + .. versionchanged:: 0.14 + The ``signer`` and ``signer_kwargs`` parameters were added to + the constructor. + """ + + #: The default serialization module to use to serialize data to a + #: string internally. The default is :mod:`json`, but can be changed + #: to any object that provides ``dumps`` and ``loads`` methods. + default_serializer: _PDataSerializer[t.Any] = json + + #: The default ``Signer`` class to instantiate when signing data. + #: The default is :class:`itsdangerous.signer.Signer`. + default_signer: type[Signer] = Signer + + #: The default fallback signers to try when unsigning fails. + default_fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = [] + + # Serializer[str] if no data serializer is provided, or if it returns str. + @t.overload + def __init__( + self: Serializer[str], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: None | _PDataSerializer[str] = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer positional argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer keyword argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a positional argument. If the strict signature of + # _PDataSerializer doesn't match, fall back to a union, requiring the user + # to specify the type. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a keyword argument. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: t.Any | None = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): + #: The list of secret keys to try for verifying signatures, from + #: oldest to newest. The newest (last) key is used for signing. + #: + #: This allows a key rotation system to keep a list of allowed + #: keys and remove expired ones. + self.secret_keys: list[bytes] = _make_keys_list(secret_key) + + if salt is not None: + salt = want_bytes(salt) + # if salt is None then the signer's default is used + + self.salt = salt + + if serializer is None: + serializer = self.default_serializer + + self.serializer: _PDataSerializer[_TSerialized] = serializer + self.is_text_serializer: bool = is_text_serializer(serializer) + + if signer is None: + signer = self.default_signer + + self.signer: type[Signer] = signer + self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {} + + if fallback_signers is None: + fallback_signers = list(self.default_fallback_signers) + + self.fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = fallback_signers + self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {} + + @property + def secret_key(self) -> bytes: + """The newest (last) entry in the :attr:`secret_keys` list. This + is for compatibility from before key rotation support was added. + """ + return self.secret_keys[-1] + + def load_payload( + self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None + ) -> t.Any: + """Loads the encoded object. This function raises + :class:`.BadPayload` if the payload is not valid. The + ``serializer`` parameter can be used to override the serializer + stored on the class. The encoded ``payload`` should always be + bytes. + """ + if serializer is None: + use_serializer = self.serializer + is_text = self.is_text_serializer + else: + use_serializer = serializer + is_text = is_text_serializer(serializer) + + try: + if is_text: + return use_serializer.loads(payload.decode("utf-8")) # type: ignore[arg-type] + + return use_serializer.loads(payload) # type: ignore[arg-type] + except Exception as e: + raise BadPayload( + "Could not load the payload because an exception" + " occurred on unserializing the data.", + original_error=e, + ) from e + + def dump_payload(self, obj: t.Any) -> bytes: + """Dumps the encoded object. The return value is always bytes. + If the internal serializer returns text, the value will be + encoded as UTF-8. + """ + return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs)) + + def make_signer(self, salt: str | bytes | None = None) -> Signer: + """Creates a new instance of the signer to be used. The default + implementation uses the :class:`.Signer` base class. + """ + if salt is None: + salt = self.salt + + return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs) + + def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]: + """Iterates over all signers to be tried for unsigning. Starts + with the configured signer, then constructs each signer + specified in ``fallback_signers``. + """ + if salt is None: + salt = self.salt + + yield self.make_signer(salt) + + for fallback in self.fallback_signers: + if isinstance(fallback, dict): + kwargs = fallback + fallback = self.signer + elif isinstance(fallback, tuple): + fallback, kwargs = fallback + else: + kwargs = self.signer_kwargs + + for secret_key in self.secret_keys: + yield fallback(secret_key, salt=salt, **kwargs) + + def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized: + """Returns a signed string serialized with the internal + serializer. The return value can be either a byte or unicode + string depending on the format of the internal serializer. + """ + payload = want_bytes(self.dump_payload(obj)) + rv = self.make_signer(salt).sign(payload) + + if self.is_text_serializer: + return rv.decode("utf-8") # type: ignore[return-value] + + return rv # type: ignore[return-value] + + def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None: + """Like :meth:`dumps` but dumps into a file. The file handle has + to be compatible with what the internal serializer expects. + """ + f.write(self.dumps(obj, salt)) + + def loads( + self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any + ) -> t.Any: + """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the + signature validation fails. + """ + s = want_bytes(s) + last_exception = None + + for signer in self.iter_unsigners(salt): + try: + return self.load_payload(signer.unsign(s)) + except BadSignature as err: + last_exception = err + + raise t.cast(BadSignature, last_exception) + + def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any: + """Like :meth:`loads` but loads from a file.""" + return self.loads(f.read(), salt) + + def loads_unsafe( + self, s: str | bytes, salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: + """Like :meth:`loads` but without verifying the signature. This + is potentially very dangerous to use depending on how your + serializer works. The return value is ``(signature_valid, + payload)`` instead of just the payload. The first item will be a + boolean that indicates if the signature is valid. This function + never fails. + + Use it for debugging only and if you know that your serializer + module is not exploitable (for example, do not use it with a + pickle serializer). + + .. versionadded:: 0.15 + """ + return self._loads_unsafe_impl(s, salt) + + def _loads_unsafe_impl( + self, + s: str | bytes, + salt: str | bytes | None, + load_kwargs: dict[str, t.Any] | None = None, + load_payload_kwargs: dict[str, t.Any] | None = None, + ) -> tuple[bool, t.Any]: + """Low level helper function to implement :meth:`loads_unsafe` + in serializer subclasses. + """ + if load_kwargs is None: + load_kwargs = {} + + try: + return True, self.loads(s, salt=salt, **load_kwargs) + except BadSignature as e: + if e.payload is None: + return False, None + + if load_payload_kwargs is None: + load_payload_kwargs = {} + + try: + return ( + False, + self.load_payload(e.payload, **load_payload_kwargs), + ) + except BadPayload: + return False, None + + def load_unsafe( + self, f: t.IO[t.Any], salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: + """Like :meth:`loads_unsafe` but loads from a file. + + .. versionadded:: 0.15 + """ + return self.loads_unsafe(f.read(), salt=salt) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/signer.py b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/signer.py new file mode 100644 index 0000000..e324dc0 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/signer.py @@ -0,0 +1,266 @@ +from __future__ import annotations + +import collections.abc as cabc +import hashlib +import hmac +import typing as t + +from .encoding import _base64_alphabet +from .encoding import base64_decode +from .encoding import base64_encode +from .encoding import want_bytes +from .exc import BadSignature + + +class SigningAlgorithm: + """Subclasses must implement :meth:`get_signature` to provide + signature generation functionality. + """ + + def get_signature(self, key: bytes, value: bytes) -> bytes: + """Returns the signature for the given key and value.""" + raise NotImplementedError() + + def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool: + """Verifies the given signature matches the expected + signature. + """ + return hmac.compare_digest(sig, self.get_signature(key, value)) + + +class NoneAlgorithm(SigningAlgorithm): + """Provides an algorithm that does not perform any signing and + returns an empty signature. + """ + + def get_signature(self, key: bytes, value: bytes) -> bytes: + return b"" + + +def _lazy_sha1(string: bytes = b"") -> t.Any: + """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include + SHA-1, in which case the import and use as a default would fail before the + developer can configure something else. + """ + return hashlib.sha1(string) + + +class HMACAlgorithm(SigningAlgorithm): + """Provides signature generation using HMACs.""" + + #: The digest method to use with the MAC algorithm. This defaults to + #: SHA1, but can be changed to any other function in the hashlib + #: module. + default_digest_method: t.Any = staticmethod(_lazy_sha1) + + def __init__(self, digest_method: t.Any = None): + if digest_method is None: + digest_method = self.default_digest_method + + self.digest_method: t.Any = digest_method + + def get_signature(self, key: bytes, value: bytes) -> bytes: + mac = hmac.new(key, msg=value, digestmod=self.digest_method) + return mac.digest() + + +def _make_keys_list( + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], +) -> list[bytes]: + if isinstance(secret_key, (str, bytes)): + return [want_bytes(secret_key)] + + return [want_bytes(s) for s in secret_key] # pyright: ignore + + +class Signer: + """A signer securely signs bytes, then unsigns them to verify that + the value hasn't been changed. + + The secret key should be a random string of ``bytes`` and should not + be saved to code or version control. Different salts should be used + to distinguish signing in different contexts. See :doc:`/concepts` + for information about the security of the secret key and salt. + + :param secret_key: The secret key to sign and verify with. Can be a + list of keys, oldest to newest, to support key rotation. + :param salt: Extra key to combine with ``secret_key`` to distinguish + signatures in different contexts. + :param sep: Separator between the signature and value. + :param key_derivation: How to derive the signing key from the secret + key and salt. Possible values are ``concat``, ``django-concat``, + or ``hmac``. Defaults to :attr:`default_key_derivation`, which + defaults to ``django-concat``. + :param digest_method: Hash function to use when generating the HMAC + signature. Defaults to :attr:`default_digest_method`, which + defaults to :func:`hashlib.sha1`. Note that the security of the + hash alone doesn't apply when used intermediately in HMAC. + :param algorithm: A :class:`SigningAlgorithm` instance to use + instead of building a default :class:`HMACAlgorithm` with the + ``digest_method``. + + .. versionchanged:: 2.0 + Added support for key rotation by passing a list to + ``secret_key``. + + .. versionchanged:: 0.18 + ``algorithm`` was added as an argument to the class constructor. + + .. versionchanged:: 0.14 + ``key_derivation`` and ``digest_method`` were added as arguments + to the class constructor. + """ + + #: The default digest method to use for the signer. The default is + #: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or + #: compatible object. Note that the security of the hash alone + #: doesn't apply when used intermediately in HMAC. + #: + #: .. versionadded:: 0.14 + default_digest_method: t.Any = staticmethod(_lazy_sha1) + + #: The default scheme to use to derive the signing key from the + #: secret key and salt. The default is ``django-concat``. Possible + #: values are ``concat``, ``django-concat``, and ``hmac``. + #: + #: .. versionadded:: 0.14 + default_key_derivation: str = "django-concat" + + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous.Signer", + sep: str | bytes = b".", + key_derivation: str | None = None, + digest_method: t.Any | None = None, + algorithm: SigningAlgorithm | None = None, + ): + #: The list of secret keys to try for verifying signatures, from + #: oldest to newest. The newest (last) key is used for signing. + #: + #: This allows a key rotation system to keep a list of allowed + #: keys and remove expired ones. + self.secret_keys: list[bytes] = _make_keys_list(secret_key) + self.sep: bytes = want_bytes(sep) + + if self.sep in _base64_alphabet: + raise ValueError( + "The given separator cannot be used because it may be" + " contained in the signature itself. ASCII letters," + " digits, and '-_=' must not be used." + ) + + if salt is not None: + salt = want_bytes(salt) + else: + salt = b"itsdangerous.Signer" + + self.salt = salt + + if key_derivation is None: + key_derivation = self.default_key_derivation + + self.key_derivation: str = key_derivation + + if digest_method is None: + digest_method = self.default_digest_method + + self.digest_method: t.Any = digest_method + + if algorithm is None: + algorithm = HMACAlgorithm(self.digest_method) + + self.algorithm: SigningAlgorithm = algorithm + + @property + def secret_key(self) -> bytes: + """The newest (last) entry in the :attr:`secret_keys` list. This + is for compatibility from before key rotation support was added. + """ + return self.secret_keys[-1] + + def derive_key(self, secret_key: str | bytes | None = None) -> bytes: + """This method is called to derive the key. The default key + derivation choices can be overridden here. Key derivation is not + intended to be used as a security method to make a complex key + out of a short password. Instead you should use large random + secret keys. + + :param secret_key: A specific secret key to derive from. + Defaults to the last item in :attr:`secret_keys`. + + .. versionchanged:: 2.0 + Added the ``secret_key`` parameter. + """ + if secret_key is None: + secret_key = self.secret_keys[-1] + else: + secret_key = want_bytes(secret_key) + + if self.key_derivation == "concat": + return t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) + elif self.key_derivation == "django-concat": + return t.cast( + bytes, self.digest_method(self.salt + b"signer" + secret_key).digest() + ) + elif self.key_derivation == "hmac": + mac = hmac.new(secret_key, digestmod=self.digest_method) + mac.update(self.salt) + return mac.digest() + elif self.key_derivation == "none": + return secret_key + else: + raise TypeError("Unknown key derivation method") + + def get_signature(self, value: str | bytes) -> bytes: + """Returns the signature for the given value.""" + value = want_bytes(value) + key = self.derive_key() + sig = self.algorithm.get_signature(key, value) + return base64_encode(sig) + + def sign(self, value: str | bytes) -> bytes: + """Signs the given string.""" + value = want_bytes(value) + return value + self.sep + self.get_signature(value) + + def verify_signature(self, value: str | bytes, sig: str | bytes) -> bool: + """Verifies the signature for the given value.""" + try: + sig = base64_decode(sig) + except Exception: + return False + + value = want_bytes(value) + + for secret_key in reversed(self.secret_keys): + key = self.derive_key(secret_key) + + if self.algorithm.verify_signature(key, value, sig): + return True + + return False + + def unsign(self, signed_value: str | bytes) -> bytes: + """Unsigns the given string.""" + signed_value = want_bytes(signed_value) + + if self.sep not in signed_value: + raise BadSignature(f"No {self.sep!r} found in value") + + value, sig = signed_value.rsplit(self.sep, 1) + + if self.verify_signature(value, sig): + return value + + raise BadSignature(f"Signature {sig!r} does not match", payload=value) + + def validate(self, signed_value: str | bytes) -> bool: + """Only validates the given signed value. Returns ``True`` if + the signature exists and is valid. + """ + try: + self.unsign(signed_value) + return True + except BadSignature: + return False diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/timed.py b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/timed.py new file mode 100644 index 0000000..7384375 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/timed.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +import collections.abc as cabc +import time +import typing as t +from datetime import datetime +from datetime import timezone + +from .encoding import base64_decode +from .encoding import base64_encode +from .encoding import bytes_to_int +from .encoding import int_to_bytes +from .encoding import want_bytes +from .exc import BadSignature +from .exc import BadTimeSignature +from .exc import SignatureExpired +from .serializer import _TSerialized +from .serializer import Serializer +from .signer import Signer + + +class TimestampSigner(Signer): + """Works like the regular :class:`.Signer` but also records the time + of the signing and can be used to expire signatures. The + :meth:`unsign` method can raise :exc:`.SignatureExpired` if the + unsigning failed because the signature is expired. + """ + + def get_timestamp(self) -> int: + """Returns the current timestamp. The function must return an + integer. + """ + return int(time.time()) + + def timestamp_to_datetime(self, ts: int) -> datetime: + """Convert the timestamp from :meth:`get_timestamp` into an + aware :class`datetime.datetime` in UTC. + + .. versionchanged:: 2.0 + The timestamp is returned as a timezone-aware ``datetime`` + in UTC rather than a naive ``datetime`` assumed to be UTC. + """ + return datetime.fromtimestamp(ts, tz=timezone.utc) + + def sign(self, value: str | bytes) -> bytes: + """Signs the given string and also attaches time information.""" + value = want_bytes(value) + timestamp = base64_encode(int_to_bytes(self.get_timestamp())) + sep = want_bytes(self.sep) + value = value + sep + timestamp + return value + sep + self.get_signature(value) + + # Ignore overlapping signatures check, return_timestamp is the only + # parameter that affects the return type. + + @t.overload + def unsign( # type: ignore[overload-overlap] + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[False] = False, + ) -> bytes: ... + + @t.overload + def unsign( + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[True] = True, + ) -> tuple[bytes, datetime]: ... + + def unsign( + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: bool = False, + ) -> tuple[bytes, datetime] | bytes: + """Works like the regular :meth:`.Signer.unsign` but can also + validate the time. See the base docstring of the class for + the general behavior. If ``return_timestamp`` is ``True`` the + timestamp of the signature will be returned as an aware + :class:`datetime.datetime` object in UTC. + + .. versionchanged:: 2.0 + The timestamp is returned as a timezone-aware ``datetime`` + in UTC rather than a naive ``datetime`` assumed to be UTC. + """ + try: + result = super().unsign(signed_value) + sig_error = None + except BadSignature as e: + sig_error = e + result = e.payload or b"" + + sep = want_bytes(self.sep) + + # If there is no timestamp in the result there is something + # seriously wrong. In case there was a signature error, we raise + # that one directly, otherwise we have a weird situation in + # which we shouldn't have come except someone uses a time-based + # serializer on non-timestamp data, so catch that. + if sep not in result: + if sig_error: + raise sig_error + + raise BadTimeSignature("timestamp missing", payload=result) + + value, ts_bytes = result.rsplit(sep, 1) + ts_int: int | None = None + ts_dt: datetime | None = None + + try: + ts_int = bytes_to_int(base64_decode(ts_bytes)) + except Exception: + pass + + # Signature is *not* okay. Raise a proper error now that we have + # split the value and the timestamp. + if sig_error is not None: + if ts_int is not None: + try: + ts_dt = self.timestamp_to_datetime(ts_int) + except (ValueError, OSError, OverflowError) as exc: + # Windows raises OSError + # 32-bit raises OverflowError + raise BadTimeSignature( + "Malformed timestamp", payload=value + ) from exc + + raise BadTimeSignature(str(sig_error), payload=value, date_signed=ts_dt) + + # Signature was okay but the timestamp is actually not there or + # malformed. Should not happen, but we handle it anyway. + if ts_int is None: + raise BadTimeSignature("Malformed timestamp", payload=value) + + # Check timestamp is not older than max_age + if max_age is not None: + age = self.get_timestamp() - ts_int + + if age > max_age: + raise SignatureExpired( + f"Signature age {age} > {max_age} seconds", + payload=value, + date_signed=self.timestamp_to_datetime(ts_int), + ) + + if age < 0: + raise SignatureExpired( + f"Signature age {age} < 0 seconds", + payload=value, + date_signed=self.timestamp_to_datetime(ts_int), + ) + + if return_timestamp: + return value, self.timestamp_to_datetime(ts_int) + + return value + + def validate(self, signed_value: str | bytes, max_age: int | None = None) -> bool: + """Only validates the given signed value. Returns ``True`` if + the signature exists and is valid.""" + try: + self.unsign(signed_value, max_age=max_age) + return True + except BadSignature: + return False + + +class TimedSerializer(Serializer[_TSerialized]): + """Uses :class:`TimestampSigner` instead of the default + :class:`.Signer`. + """ + + default_signer: type[TimestampSigner] = TimestampSigner + + def iter_unsigners( + self, salt: str | bytes | None = None + ) -> cabc.Iterator[TimestampSigner]: + return t.cast("cabc.Iterator[TimestampSigner]", super().iter_unsigners(salt)) + + # TODO: Signature is incompatible because parameters were added + # before salt. + + def loads( # type: ignore[override] + self, + s: str | bytes, + max_age: int | None = None, + return_timestamp: bool = False, + salt: str | bytes | None = None, + ) -> t.Any: + """Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the + signature validation fails. If a ``max_age`` is provided it will + ensure the signature is not older than that time in seconds. In + case the signature is outdated, :exc:`.SignatureExpired` is + raised. All arguments are forwarded to the signer's + :meth:`~TimestampSigner.unsign` method. + """ + s = want_bytes(s) + last_exception = None + + for signer in self.iter_unsigners(salt): + try: + base64d, timestamp = signer.unsign( + s, max_age=max_age, return_timestamp=True + ) + payload = self.load_payload(base64d) + + if return_timestamp: + return payload, timestamp + + return payload + except SignatureExpired: + # The signature was unsigned successfully but was + # expired. Do not try the next signer. + raise + except BadSignature as err: + last_exception = err + + raise t.cast(BadSignature, last_exception) + + def loads_unsafe( # type: ignore[override] + self, + s: str | bytes, + max_age: int | None = None, + salt: str | bytes | None = None, + ) -> tuple[bool, t.Any]: + return self._loads_unsafe_impl(s, salt, load_kwargs={"max_age": max_age}) diff --git a/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/url_safe.py b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/url_safe.py new file mode 100644 index 0000000..56a0793 --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/itsdangerous/url_safe.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import typing as t +import zlib + +from ._json import _CompactJSON +from .encoding import base64_decode +from .encoding import base64_encode +from .exc import BadPayload +from .serializer import _PDataSerializer +from .serializer import Serializer +from .timed import TimedSerializer + + +class URLSafeSerializerMixin(Serializer[str]): + """Mixed in with a regular serializer it will attempt to zlib + compress the string to make it shorter if necessary. It will also + base64 encode the string so that it can safely be placed in a URL. + """ + + default_serializer: _PDataSerializer[str] = _CompactJSON + + def load_payload( + self, + payload: bytes, + *args: t.Any, + serializer: t.Any | None = None, + **kwargs: t.Any, + ) -> t.Any: + decompress = False + + if payload.startswith(b"."): + payload = payload[1:] + decompress = True + + try: + json = base64_decode(payload) + except Exception as e: + raise BadPayload( + "Could not base64 decode the payload because of an exception", + original_error=e, + ) from e + + if decompress: + try: + json = zlib.decompress(json) + except Exception as e: + raise BadPayload( + "Could not zlib decompress the payload before decoding the payload", + original_error=e, + ) from e + + return super().load_payload(json, *args, **kwargs) + + def dump_payload(self, obj: t.Any) -> bytes: + json = super().dump_payload(obj) + is_compressed = False + compressed = zlib.compress(json) + + if len(compressed) < (len(json) - 1): + json = compressed + is_compressed = True + + base64d = base64_encode(json) + + if is_compressed: + base64d = b"." + base64d + + return base64d + + +class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]): + """Works like :class:`.Serializer` but dumps and loads into a URL + safe string consisting of the upper and lowercase character of the + alphabet as well as ``'_'``, ``'-'`` and ``'.'``. + """ + + +class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer[str]): + """Works like :class:`.TimedSerializer` but dumps and loads into a + URL safe string consisting of the upper and lowercase character of + the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. + """ diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/INSTALLER b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/METADATA b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/METADATA new file mode 100644 index 0000000..ffef2ff --- /dev/null +++ b/web_viewer/.venv/lib/python3.12/site-packages/jinja2-3.1.6.dist-info/METADATA @@ -0,0 +1,84 @@ +Metadata-Version: 2.4 +Name: Jinja2 +Version: 3.1.6 +Summary: A very fast and expressive template engine. +Maintainer-email: Pallets +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Text Processing :: Markup :: HTML +Classifier: Typing :: Typed +License-File: LICENSE.txt +Requires-Dist: MarkupSafe>=2.0 +Requires-Dist: Babel>=2.7 ; extra == "i18n" +Project-URL: Changes, https://jinja.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://jinja.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/jinja/ +Provides-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. + + +## In A Nutshell + +```jinja +{% extends "base.html" %} +{% block title %}Members{% endblock %} +{% block content %} +