From a664449d1f2330eec6c737ccbf6b432b5b579d33 Mon Sep 17 00:00:00 2001 From: Sam Mirazi Date: Fri, 30 May 2025 19:12:47 -0700 Subject: [PATCH] phase 0 - Complete --- .cursor/rules/Agent_Requested.mdc | 40 + .cursor/rules/Always.mdc | 74 + .cursor/rules/Auto_Attached_Blank.mdc | 4 + .cursor/rules/Manual.mdc | 28 + .gitignore | 41 + .../MarkupSafe-3.0.2.dist-info/INSTALLER | 1 + .../MarkupSafe-3.0.2.dist-info/LICENSE.txt | 28 + .../MarkupSafe-3.0.2.dist-info/METADATA | 92 + .../MarkupSafe-3.0.2.dist-info/RECORD | 14 + .../MarkupSafe-3.0.2.dist-info/WHEEL | 5 + .../MarkupSafe-3.0.2.dist-info/top_level.txt | 1 + .../__pycache__/py.cpython-312.pyc | Bin 0 -> 515 bytes .../typing_extensions.cpython-312.pyc | Bin 0 -> 173377 bytes .venv/Lib/site-packages/_pytest/__init__.py | 13 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 503 bytes .../__pycache__/_argcomplete.cpython-312.pyc | Bin 0 -> 4848 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 665 bytes .../__pycache__/cacheprovider.cpython-312.pyc | Bin 0 -> 30885 bytes .../__pycache__/capture.cpython-312.pyc | Bin 0 -> 54040 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 12548 bytes .../__pycache__/debugging.cpython-312.pyc | Bin 0 -> 17940 bytes .../__pycache__/deprecated.cpython-312.pyc | Bin 0 -> 2585 bytes .../__pycache__/doctest.cpython-312.pyc | Bin 0 -> 33370 bytes .../__pycache__/faulthandler.cpython-312.pyc | Bin 0 -> 4451 bytes .../__pycache__/fixtures.cpython-312.pyc | Bin 0 -> 78683 bytes .../freeze_support.cpython-312.pyc | Bin 0 -> 1838 bytes .../__pycache__/helpconfig.cpython-312.pyc | Bin 0 -> 12255 bytes .../__pycache__/hookspec.cpython-312.pyc | Bin 0 -> 45602 bytes .../__pycache__/junitxml.cpython-312.pyc | Bin 0 -> 33278 bytes .../__pycache__/legacypath.cpython-312.pyc | Bin 0 -> 24192 bytes .../__pycache__/logging.cpython-312.pyc | Bin 0 -> 45692 bytes .../_pytest/__pycache__/main.cpython-312.pyc | Bin 0 -> 43916 bytes .../__pycache__/monkeypatch.cpython-312.pyc | Bin 0 -> 16529 bytes .../_pytest/__pycache__/nodes.cpython-312.pyc | Bin 0 -> 30760 bytes .../__pycache__/outcomes.cpython-312.pyc | Bin 0 -> 11808 bytes .../__pycache__/pastebin.cpython-312.pyc | Bin 0 -> 5677 bytes .../__pycache__/pathlib.cpython-312.pyc | Bin 0 -> 41232 bytes .../__pycache__/pytester.cpython-312.pyc | Bin 0 -> 84154 bytes .../pytester_assertions.cpython-312.pyc | Bin 0 -> 2503 bytes .../__pycache__/python.cpython-312.pyc | Bin 0 -> 70410 bytes .../__pycache__/python_api.cpython-312.pyc | Bin 0 -> 44425 bytes .../__pycache__/python_path.cpython-312.pyc | Bin 0 -> 1617 bytes .../__pycache__/recwarn.cpython-312.pyc | Bin 0 -> 16063 bytes .../__pycache__/reports.cpython-312.pyc | Bin 0 -> 24576 bytes .../__pycache__/runner.cpython-312.pyc | Bin 0 -> 23695 bytes .../_pytest/__pycache__/scope.cpython-312.pyc | Bin 0 -> 3728 bytes .../__pycache__/setuponly.cpython-312.pyc | Bin 0 -> 5476 bytes .../__pycache__/setupplan.cpython-312.pyc | Bin 0 -> 1928 bytes .../__pycache__/skipping.cpython-312.pyc | Bin 0 -> 13623 bytes .../_pytest/__pycache__/stash.cpython-312.pyc | Bin 0 -> 4391 bytes .../__pycache__/stepwise.cpython-312.pyc | Bin 0 -> 5513 bytes .../__pycache__/terminal.cpython-312.pyc | Bin 0 -> 75746 bytes .../threadexception.cpython-312.pyc | Bin 0 -> 5202 bytes .../__pycache__/timing.cpython-312.pyc | Bin 0 -> 655 bytes .../__pycache__/tmpdir.cpython-312.pyc | Bin 0 -> 12846 bytes .../__pycache__/unittest.cpython-312.pyc | Bin 0 -> 19224 bytes .../unraisableexception.cpython-312.pyc | Bin 0 -> 5265 bytes .../__pycache__/warning_types.cpython-312.pyc | Bin 0 -> 6827 bytes .../__pycache__/warnings.cpython-312.pyc | Bin 0 -> 6738 bytes .../Lib/site-packages/_pytest/_argcomplete.py | 117 + .../site-packages/_pytest/_code/__init__.py | 26 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 709 bytes .../_code/__pycache__/code.cpython-312.pyc | Bin 0 -> 62331 bytes .../_code/__pycache__/source.cpython-312.pyc | Bin 0 -> 11199 bytes .venv/Lib/site-packages/_pytest/_code/code.py | 1409 +++ .../Lib/site-packages/_pytest/_code/source.py | 215 + .../Lib/site-packages/_pytest/_io/__init__.py | 10 + .../_io/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 400 bytes .../_io/__pycache__/pprint.cpython-312.pyc | Bin 0 -> 24386 bytes .../_io/__pycache__/saferepr.cpython-312.pyc | Bin 0 -> 5846 bytes .../terminalwriter.cpython-312.pyc | Bin 0 -> 11307 bytes .../_io/__pycache__/wcwidth.cpython-312.pyc | Bin 0 -> 1706 bytes .venv/Lib/site-packages/_pytest/_io/pprint.py | 673 ++ .../Lib/site-packages/_pytest/_io/saferepr.py | 130 + .../_pytest/_io/terminalwriter.py | 274 + .../Lib/site-packages/_pytest/_io/wcwidth.py | 57 + .../Lib/site-packages/_pytest/_py/__init__.py | 0 .../_py/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 208 bytes .../_py/__pycache__/error.cpython-312.pyc | Bin 0 -> 4814 bytes .../_py/__pycache__/path.cpython-312.pyc | Bin 0 -> 68711 bytes .venv/Lib/site-packages/_pytest/_py/error.py | 111 + .venv/Lib/site-packages/_pytest/_py/path.py | 1475 +++ .venv/Lib/site-packages/_pytest/_version.py | 21 + .../_pytest/assertion/__init__.py | 192 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 9471 bytes .../__pycache__/rewrite.cpython-312.pyc | Bin 0 -> 61811 bytes .../__pycache__/truncate.cpython-312.pyc | Bin 0 -> 3747 bytes .../__pycache__/util.cpython-312.pyc | Bin 0 -> 23616 bytes .../_pytest/assertion/rewrite.py | 1214 +++ .../_pytest/assertion/truncate.py | 117 + .../site-packages/_pytest/assertion/util.py | 609 ++ .../site-packages/_pytest/cacheprovider.py | 626 ++ .venv/Lib/site-packages/_pytest/capture.py | 1105 +++ .venv/Lib/site-packages/_pytest/compat.py | 351 + .../site-packages/_pytest/config/__init__.py | 1973 ++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 81317 bytes .../__pycache__/argparsing.cpython-312.pyc | Bin 0 -> 26047 bytes .../config/__pycache__/compat.cpython-312.pyc | Bin 0 -> 3589 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 819 bytes .../__pycache__/findpaths.cpython-312.pyc | Bin 0 -> 9130 bytes .../_pytest/config/argparsing.py | 551 ++ .../site-packages/_pytest/config/compat.py | 85 + .../_pytest/config/exceptions.py | 13 + .../site-packages/_pytest/config/findpaths.py | 228 + .venv/Lib/site-packages/_pytest/debugging.py | 385 + .venv/Lib/site-packages/_pytest/deprecated.py | 91 + .venv/Lib/site-packages/_pytest/doctest.py | 755 ++ .../Lib/site-packages/_pytest/faulthandler.py | 105 + .venv/Lib/site-packages/_pytest/fixtures.py | 1932 ++++ .../site-packages/_pytest/freeze_support.py | 45 + .venv/Lib/site-packages/_pytest/helpconfig.py | 276 + .venv/Lib/site-packages/_pytest/hookspec.py | 1333 +++ .venv/Lib/site-packages/_pytest/junitxml.py | 697 ++ .venv/Lib/site-packages/_pytest/legacypath.py | 468 + .venv/Lib/site-packages/_pytest/logging.py | 955 ++ .venv/Lib/site-packages/_pytest/main.py | 1072 ++ .../site-packages/_pytest/mark/__init__.py | 292 + .../mark/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 12517 bytes .../__pycache__/expression.cpython-312.pyc | Bin 0 -> 16547 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 25716 bytes .../site-packages/_pytest/mark/expression.py | 333 + .../site-packages/_pytest/mark/structures.py | 615 ++ .../Lib/site-packages/_pytest/monkeypatch.py | 415 + .venv/Lib/site-packages/_pytest/nodes.py | 766 ++ .venv/Lib/site-packages/_pytest/outcomes.py | 318 + .venv/Lib/site-packages/_pytest/pastebin.py | 113 + .venv/Lib/site-packages/_pytest/pathlib.py | 1055 ++ .venv/Lib/site-packages/_pytest/py.typed | 0 .venv/Lib/site-packages/_pytest/pytester.py | 1766 ++++ .../_pytest/pytester_assertions.py | 74 + .venv/Lib/site-packages/_pytest/python.py | 1679 ++++ .venv/Lib/site-packages/_pytest/python_api.py | 1028 ++ .../Lib/site-packages/_pytest/python_path.py | 26 + .venv/Lib/site-packages/_pytest/recwarn.py | 364 + .venv/Lib/site-packages/_pytest/reports.py | 636 ++ .venv/Lib/site-packages/_pytest/runner.py | 571 ++ .venv/Lib/site-packages/_pytest/scope.py | 91 + .venv/Lib/site-packages/_pytest/setuponly.py | 102 + .venv/Lib/site-packages/_pytest/setupplan.py | 39 + .venv/Lib/site-packages/_pytest/skipping.py | 301 + .venv/Lib/site-packages/_pytest/stash.py | 116 + .venv/Lib/site-packages/_pytest/stepwise.py | 125 + .venv/Lib/site-packages/_pytest/terminal.py | 1577 +++ .../site-packages/_pytest/threadexception.py | 97 + .venv/Lib/site-packages/_pytest/timing.py | 16 + .venv/Lib/site-packages/_pytest/tmpdir.py | 314 + .venv/Lib/site-packages/_pytest/unittest.py | 435 + .../_pytest/unraisableexception.py | 100 + .../site-packages/_pytest/warning_types.py | 166 + .venv/Lib/site-packages/_pytest/warnings.py | 151 + .../annotated_types-0.7.0.dist-info/INSTALLER | 1 + .../annotated_types-0.7.0.dist-info/METADATA | 295 + .../annotated_types-0.7.0.dist-info/RECORD | 10 + .../annotated_types-0.7.0.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .../site-packages/annotated_types/__init__.py | 432 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 18643 bytes .../__pycache__/test_cases.cpython-312.pyc | Bin 0 -> 13256 bytes .../site-packages/annotated_types/py.typed | 0 .../annotated_types/test_cases.py | 151 + .../anyio-4.9.0.dist-info/INSTALLER | 1 + .../anyio-4.9.0.dist-info/LICENSE | 20 + .../anyio-4.9.0.dist-info/METADATA | 105 + .../anyio-4.9.0.dist-info/RECORD | 88 + .../site-packages/anyio-4.9.0.dist-info/WHEEL | 5 + .../anyio-4.9.0.dist-info/entry_points.txt | 2 + .../anyio-4.9.0.dist-info/top_level.txt | 1 + .venv/Lib/site-packages/anyio/__init__.py | 85 + .../__init__.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 3714 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3600 bytes .../from_thread.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 25292 bytes .../__pycache__/from_thread.cpython-312.pyc | Bin 0 -> 24066 bytes .../lowlevel.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 7101 bytes .../__pycache__/lowlevel.cpython-312.pyc | Bin 0 -> 6988 bytes ...pytest_plugin.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 14583 bytes .../__pycache__/pytest_plugin.cpython-312.pyc | Bin 0 -> 13378 bytes .../to_interpreter.cpython-312.pyc | Bin 0 -> 9118 bytes .../__pycache__/to_process.cpython-312.pyc | Bin 0 -> 11813 bytes .../to_thread.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 3074 bytes .../__pycache__/to_thread.cpython-312.pyc | Bin 0 -> 2961 bytes .../site-packages/anyio/_backends/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 212 bytes .../__pycache__/_asyncio.cpython-312.pyc | Bin 0 -> 132430 bytes .../__pycache__/_trio.cpython-312.pyc | Bin 0 -> 70333 bytes .../site-packages/anyio/_backends/_asyncio.py | 2816 ++++++ .../site-packages/anyio/_backends/_trio.py | 1334 +++ .../Lib/site-packages/anyio/_core/__init__.py | 0 .../__init__.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 208 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 208 bytes .../_asyncio_selector_thread.cpython-312.pyc | Bin 0 -> 8423 bytes .../_eventloop.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 6479 bytes .../__pycache__/_eventloop.cpython-312.pyc | Bin 0 -> 6366 bytes .../_exceptions.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 6188 bytes .../__pycache__/_exceptions.cpython-312.pyc | Bin 0 -> 6075 bytes .../_fileio.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 41745 bytes .../_core/__pycache__/_fileio.cpython-312.pyc | Bin 0 -> 41632 bytes .../_resources.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 1064 bytes .../__pycache__/_resources.cpython-312.pyc | Bin 0 -> 950 bytes .../_signals.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 1407 bytes .../__pycache__/_signals.cpython-312.pyc | Bin 0 -> 1293 bytes .../_sockets.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 31637 bytes .../__pycache__/_sockets.cpython-312.pyc | Bin 0 -> 31524 bytes .../_streams.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 2469 bytes .../__pycache__/_streams.cpython-312.pyc | Bin 0 -> 2356 bytes ..._subprocesses.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 9761 bytes .../__pycache__/_subprocesses.cpython-312.pyc | Bin 0 -> 9648 bytes ...nchronization.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 32292 bytes .../_synchronization.cpython-312.pyc | Bin 0 -> 32179 bytes .../_tasks.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 7121 bytes .../_core/__pycache__/_tasks.cpython-312.pyc | Bin 0 -> 7008 bytes .../_tempfile.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 28265 bytes .../__pycache__/_tempfile.cpython-312.pyc | Bin 0 -> 28152 bytes .../_testing.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 3702 bytes .../__pycache__/_testing.cpython-312.pyc | Bin 0 -> 3588 bytes .../_typedattr.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 3967 bytes .../__pycache__/_typedattr.cpython-312.pyc | Bin 0 -> 3853 bytes .../anyio/_core/_asyncio_selector_thread.py | 167 + .../site-packages/anyio/_core/_eventloop.py | 166 + .../site-packages/anyio/_core/_exceptions.py | 126 + .../Lib/site-packages/anyio/_core/_fileio.py | 742 ++ .../site-packages/anyio/_core/_resources.py | 18 + .../Lib/site-packages/anyio/_core/_signals.py | 27 + .../Lib/site-packages/anyio/_core/_sockets.py | 792 ++ .../Lib/site-packages/anyio/_core/_streams.py | 52 + .../anyio/_core/_subprocesses.py | 202 + .../anyio/_core/_synchronization.py | 732 ++ .venv/Lib/site-packages/anyio/_core/_tasks.py | 158 + .../site-packages/anyio/_core/_tempfile.py | 616 ++ .../Lib/site-packages/anyio/_core/_testing.py | 78 + .../site-packages/anyio/_core/_typedattr.py | 81 + .venv/Lib/site-packages/anyio/abc/__init__.py | 55 + .../__init__.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 2357 bytes .../abc/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2243 bytes .../_eventloop.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 15101 bytes .../__pycache__/_eventloop.cpython-312.pyc | Bin 0 -> 14988 bytes .../_resources.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 1769 bytes .../__pycache__/_resources.cpython-312.pyc | Bin 0 -> 1655 bytes .../_sockets.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 9993 bytes .../abc/__pycache__/_sockets.cpython-312.pyc | Bin 0 -> 9880 bytes .../_streams.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 8573 bytes .../abc/__pycache__/_streams.cpython-312.pyc | Bin 0 -> 8459 bytes ..._subprocesses.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 3377 bytes .../__pycache__/_subprocesses.cpython-312.pyc | Bin 0 -> 3263 bytes .../_tasks.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 4652 bytes .../abc/__pycache__/_tasks.cpython-312.pyc | Bin 0 -> 4539 bytes .../_testing.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 2971 bytes .../abc/__pycache__/_testing.cpython-312.pyc | Bin 0 -> 2858 bytes .../Lib/site-packages/anyio/abc/_eventloop.py | 376 + .../Lib/site-packages/anyio/abc/_resources.py | 33 + .venv/Lib/site-packages/anyio/abc/_sockets.py | 194 + .venv/Lib/site-packages/anyio/abc/_streams.py | 203 + .../site-packages/anyio/abc/_subprocesses.py | 79 + .venv/Lib/site-packages/anyio/abc/_tasks.py | 101 + .venv/Lib/site-packages/anyio/abc/_testing.py | 65 + .venv/Lib/site-packages/anyio/from_thread.py | 527 + .venv/Lib/site-packages/anyio/lowlevel.py | 161 + .venv/Lib/site-packages/anyio/py.typed | 0 .../Lib/site-packages/anyio/pytest_plugin.py | 272 + .../site-packages/anyio/streams/__init__.py | 0 .../__init__.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 210 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 210 bytes .../__pycache__/buffered.cpython-312.pyc | Bin 0 -> 6139 bytes .../streams/__pycache__/file.cpython-312.pyc | Bin 0 -> 7545 bytes .../memory.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 15074 bytes .../__pycache__/memory.cpython-312.pyc | Bin 0 -> 14961 bytes .../stapled.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 7635 bytes .../__pycache__/stapled.cpython-312.pyc | Bin 0 -> 7521 bytes .../streams/__pycache__/text.cpython-312.pyc | Bin 0 -> 8315 bytes .../tls.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 17693 bytes .../streams/__pycache__/tls.cpython-312.pyc | Bin 0 -> 17580 bytes .../site-packages/anyio/streams/buffered.py | 119 + .venv/Lib/site-packages/anyio/streams/file.py | 148 + .../Lib/site-packages/anyio/streams/memory.py | 317 + .../site-packages/anyio/streams/stapled.py | 141 + .venv/Lib/site-packages/anyio/streams/text.py | 147 + .venv/Lib/site-packages/anyio/streams/tls.py | 352 + .../Lib/site-packages/anyio/to_interpreter.py | 218 + .venv/Lib/site-packages/anyio/to_process.py | 258 + .venv/Lib/site-packages/anyio/to_thread.py | 69 + .../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 + .venv/Lib/site-packages/blinker/__init__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 516 bytes .../__pycache__/_utilities.cpython-312.pyc | Bin 0 -> 2743 bytes .../blinker/__pycache__/base.cpython-312.pyc | Bin 0 -> 21986 bytes .venv/Lib/site-packages/blinker/_utilities.py | 64 + .venv/Lib/site-packages/blinker/base.py | 512 + .venv/Lib/site-packages/blinker/py.typed | 0 .../certifi-2025.4.26.dist-info/INSTALLER | 1 + .../certifi-2025.4.26.dist-info/METADATA | 78 + .../certifi-2025.4.26.dist-info/RECORD | 14 + .../certifi-2025.4.26.dist-info/WHEEL | 5 + .../licenses/LICENSE | 20 + .../certifi-2025.4.26.dist-info/top_level.txt | 1 + .venv/Lib/site-packages/certifi/__init__.py | 4 + .venv/Lib/site-packages/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 331 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 646 bytes .../certifi/__pycache__/core.cpython-312.pyc | Bin 0 -> 3207 bytes .venv/Lib/site-packages/certifi/core.py | 114 + .venv/Lib/site-packages/certifi/py.typed | 0 .../INSTALLER | 1 + .../METADATA | 731 ++ .../charset_normalizer-3.4.2.dist-info/RECORD | 35 + .../charset_normalizer-3.4.2.dist-info/WHEEL | 5 + .../entry_points.txt | 2 + .../licenses/LICENSE | 21 + .../top_level.txt | 1 + .../charset_normalizer/__init__.py | 48 + .../charset_normalizer/__main__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1797 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 372 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 18158 bytes .../__pycache__/cd.cpython-312.pyc | Bin 0 -> 13259 bytes .../__pycache__/constant.cpython-312.pyc | Bin 0 -> 40827 bytes .../__pycache__/legacy.cpython-312.pyc | Bin 0 -> 2797 bytes .../__pycache__/md.cpython-312.pyc | Bin 0 -> 24353 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 17140 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 13727 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 399 bytes .../site-packages/charset_normalizer/api.py | 668 ++ .../site-packages/charset_normalizer/cd.py | 395 + .../charset_normalizer/cli/__init__.py | 8 + .../charset_normalizer/cli/__main__.py | 381 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 360 bytes .../cli/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 14410 bytes .../charset_normalizer/constant.py | 2015 ++++ .../charset_normalizer/legacy.py | 64 + .../charset_normalizer/md.cp312-win_amd64.pyd | Bin 0 -> 10752 bytes .../site-packages/charset_normalizer/md.py | 635 ++ .../md__mypyc.cp312-win_amd64.pyd | Bin 0 -> 125952 bytes .../charset_normalizer/models.py | 360 + .../site-packages/charset_normalizer/py.typed | 0 .../site-packages/charset_normalizer/utils.py | 414 + .../charset_normalizer/version.py | 8 + .../click-8.2.1.dist-info/INSTALLER | 1 + .../click-8.2.1.dist-info/METADATA | 82 + .../click-8.2.1.dist-info/RECORD | 38 + .../site-packages/click-8.2.1.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 28 + .venv/Lib/site-packages/click/__init__.py | 123 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4088 bytes .../click/__pycache__/_compat.cpython-312.pyc | Bin 0 -> 24207 bytes .../__pycache__/_termui_impl.cpython-312.pyc | Bin 0 -> 31493 bytes .../__pycache__/_textwrap.cpython-312.pyc | Bin 0 -> 2436 bytes .../__pycache__/_winconsole.cpython-312.pyc | Bin 0 -> 11781 bytes .../click/__pycache__/core.cpython-312.pyc | Bin 0 -> 127413 bytes .../__pycache__/decorators.cpython-312.pyc | Bin 0 -> 22153 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 14793 bytes .../__pycache__/formatting.cpython-312.pyc | Bin 0 -> 13614 bytes .../click/__pycache__/globals.cpython-312.pyc | Bin 0 -> 2981 bytes .../click/__pycache__/parser.cpython-312.pyc | Bin 0 -> 20203 bytes .../shell_completion.cpython-312.pyc | Bin 0 -> 23150 bytes .../click/__pycache__/termui.cpython-312.pyc | Bin 0 -> 34540 bytes .../click/__pycache__/testing.cpython-312.pyc | Bin 0 -> 26900 bytes .../click/__pycache__/types.cpython-312.pyc | Bin 0 -> 49001 bytes .../click/__pycache__/utils.cpython-312.pyc | Bin 0 -> 24885 bytes .venv/Lib/site-packages/click/_compat.py | 622 ++ .venv/Lib/site-packages/click/_termui_impl.py | 839 ++ .venv/Lib/site-packages/click/_textwrap.py | 51 + .venv/Lib/site-packages/click/_winconsole.py | 296 + .venv/Lib/site-packages/click/core.py | 3135 ++++++ .venv/Lib/site-packages/click/decorators.py | 551 ++ .venv/Lib/site-packages/click/exceptions.py | 308 + .venv/Lib/site-packages/click/formatting.py | 301 + .venv/Lib/site-packages/click/globals.py | 67 + .venv/Lib/site-packages/click/parser.py | 532 + .venv/Lib/site-packages/click/py.typed | 0 .../site-packages/click/shell_completion.py | 644 ++ .venv/Lib/site-packages/click/termui.py | 877 ++ .venv/Lib/site-packages/click/testing.py | 565 ++ .venv/Lib/site-packages/click/types.py | 1165 +++ .venv/Lib/site-packages/click/utils.py | 627 ++ .../colorama-0.4.6.dist-info/INSTALLER | 1 + .../colorama-0.4.6.dist-info/METADATA | 441 + .../colorama-0.4.6.dist-info/RECORD | 31 + .../colorama-0.4.6.dist-info/WHEEL | 5 + .../licenses/LICENSE.txt | 27 + .venv/Lib/site-packages/colorama/__init__.py | 7 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 505 bytes .../colorama/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 3958 bytes .../__pycache__/ansitowin32.cpython-312.pyc | Bin 0 -> 16425 bytes .../__pycache__/initialise.cpython-312.pyc | Bin 0 -> 3563 bytes .../__pycache__/win32.cpython-312.pyc | Bin 0 -> 8148 bytes .../__pycache__/winterm.cpython-312.pyc | Bin 0 -> 9101 bytes .venv/Lib/site-packages/colorama/ansi.py | 102 + .../Lib/site-packages/colorama/ansitowin32.py | 277 + .../Lib/site-packages/colorama/initialise.py | 121 + .../site-packages/colorama/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 211 bytes .../__pycache__/ansi_test.cpython-312.pyc | Bin 0 -> 5480 bytes .../ansitowin32_test.cpython-312.pyc | Bin 0 -> 18019 bytes .../initialise_test.cpython-312.pyc | Bin 0 -> 11667 bytes .../__pycache__/isatty_test.cpython-312.pyc | Bin 0 -> 4794 bytes .../tests/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2483 bytes .../__pycache__/winterm_test.cpython-312.pyc | Bin 0 -> 6625 bytes .../site-packages/colorama/tests/ansi_test.py | 76 + .../colorama/tests/ansitowin32_test.py | 294 + .../colorama/tests/initialise_test.py | 189 + .../colorama/tests/isatty_test.py | 57 + .../Lib/site-packages/colorama/tests/utils.py | 49 + .../colorama/tests/winterm_test.py | 131 + .venv/Lib/site-packages/colorama/win32.py | 180 + .venv/Lib/site-packages/colorama/winterm.py | 195 + .../fastapi-0.115.12.dist-info/INSTALLER | 1 + .../fastapi-0.115.12.dist-info/METADATA | 565 ++ .../fastapi-0.115.12.dist-info/RECORD | 97 + .../fastapi-0.115.12.dist-info/REQUESTED | 0 .../fastapi-0.115.12.dist-info/WHEEL | 4 + .../entry_points.txt | 5 + .../licenses/LICENSE | 21 + .venv/Lib/site-packages/fastapi/__init__.py | 25 + .venv/Lib/site-packages/fastapi/__main__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1120 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 266 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 27894 bytes .../__pycache__/applications.cpython-312.pyc | Bin 0 -> 84134 bytes .../__pycache__/background.cpython-312.pyc | Bin 0 -> 2392 bytes .../fastapi/__pycache__/cli.cpython-312.pyc | Bin 0 -> 680 bytes .../__pycache__/concurrency.cpython-312.pyc | Bin 0 -> 1693 bytes .../datastructures.cpython-312.pyc | Bin 0 -> 8165 bytes .../__pycache__/encoders.cpython-312.pyc | Bin 0 -> 10915 bytes .../exception_handlers.cpython-312.pyc | Bin 0 -> 2094 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7180 bytes .../__pycache__/logger.cpython-312.pyc | Bin 0 -> 306 bytes .../param_functions.cpython-312.pyc | Bin 0 -> 35117 bytes .../__pycache__/params.cpython-312.pyc | Bin 0 -> 25062 bytes .../__pycache__/requests.cpython-312.pyc | Bin 0 -> 295 bytes .../__pycache__/responses.cpython-312.pyc | Bin 0 -> 2423 bytes .../__pycache__/routing.cpython-312.pyc | Bin 0 -> 82038 bytes .../__pycache__/staticfiles.cpython-312.pyc | Bin 0 -> 267 bytes .../__pycache__/templating.cpython-312.pyc | Bin 0 -> 269 bytes .../__pycache__/testclient.cpython-312.pyc | Bin 0 -> 264 bytes .../fastapi/__pycache__/types.cpython-312.pyc | Bin 0 -> 775 bytes .../fastapi/__pycache__/utils.cpython-312.pyc | Bin 0 -> 8393 bytes .../__pycache__/websockets.cpython-312.pyc | Bin 0 -> 344 bytes .venv/Lib/site-packages/fastapi/_compat.py | 659 ++ .../Lib/site-packages/fastapi/applications.py | 4585 +++++++++ .venv/Lib/site-packages/fastapi/background.py | 59 + .venv/Lib/site-packages/fastapi/cli.py | 13 + .../Lib/site-packages/fastapi/concurrency.py | 39 + .../site-packages/fastapi/datastructures.py | 204 + .../fastapi/dependencies/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 217 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 2760 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 38146 bytes .../fastapi/dependencies/models.py | 37 + .../fastapi/dependencies/utils.py | 980 ++ .venv/Lib/site-packages/fastapi/encoders.py | 343 + .../fastapi/exception_handlers.py | 34 + .venv/Lib/site-packages/fastapi/exceptions.py | 176 + .venv/Lib/site-packages/fastapi/logger.py | 3 + .../fastapi/middleware/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 273 bytes .../__pycache__/cors.cpython-312.pyc | Bin 0 -> 278 bytes .../__pycache__/gzip.cpython-312.pyc | Bin 0 -> 278 bytes .../__pycache__/httpsredirect.cpython-312.pyc | Bin 0 -> 307 bytes .../__pycache__/trustedhost.cpython-312.pyc | Bin 0 -> 301 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 278 bytes .../site-packages/fastapi/middleware/cors.py | 1 + .../site-packages/fastapi/middleware/gzip.py | 1 + .../fastapi/middleware/httpsredirect.py | 3 + .../fastapi/middleware/trustedhost.py | 3 + .../site-packages/fastapi/middleware/wsgi.py | 1 + .../site-packages/fastapi/openapi/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 212 bytes .../__pycache__/constants.cpython-312.pyc | Bin 0 -> 382 bytes .../openapi/__pycache__/docs.cpython-312.pyc | Bin 0 -> 10832 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 23020 bytes .../openapi/__pycache__/utils.cpython-312.pyc | Bin 0 -> 21069 bytes .../fastapi/openapi/constants.py | 3 + .../Lib/site-packages/fastapi/openapi/docs.py | 344 + .../site-packages/fastapi/openapi/models.py | 445 + .../site-packages/fastapi/openapi/utils.py | 569 ++ .../site-packages/fastapi/param_functions.py | 2360 +++++ .venv/Lib/site-packages/fastapi/params.py | 786 ++ .venv/Lib/site-packages/fastapi/py.typed | 0 .venv/Lib/site-packages/fastapi/requests.py | 2 + .venv/Lib/site-packages/fastapi/responses.py | 48 + .venv/Lib/site-packages/fastapi/routing.py | 4439 +++++++++ .../fastapi/security/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 869 bytes .../__pycache__/api_key.cpython-312.pyc | Bin 0 -> 9720 bytes .../security/__pycache__/base.cpython-312.pyc | Bin 0 -> 531 bytes .../security/__pycache__/http.cpython-312.pyc | Bin 0 -> 13658 bytes .../__pycache__/oauth2.cpython-312.pyc | Bin 0 -> 18300 bytes .../open_id_connect_url.cpython-312.pyc | Bin 0 -> 3242 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 624 bytes .../site-packages/fastapi/security/api_key.py | 288 + .../site-packages/fastapi/security/base.py | 6 + .../site-packages/fastapi/security/http.py | 423 + .../site-packages/fastapi/security/oauth2.py | 638 ++ .../fastapi/security/open_id_connect_url.py | 84 + .../site-packages/fastapi/security/utils.py | 10 + .../Lib/site-packages/fastapi/staticfiles.py | 1 + .venv/Lib/site-packages/fastapi/templating.py | 1 + .venv/Lib/site-packages/fastapi/testclient.py | 1 + .venv/Lib/site-packages/fastapi/types.py | 10 + .venv/Lib/site-packages/fastapi/utils.py | 220 + .venv/Lib/site-packages/fastapi/websockets.py | 3 + .../flask-3.1.1.dist-info/INSTALLER | 1 + .../flask-3.1.1.dist-info/METADATA | 89 + .../flask-3.1.1.dist-info/RECORD | 58 + .../flask-3.1.1.dist-info/REQUESTED | 0 .../site-packages/flask-3.1.1.dist-info/WHEEL | 4 + .../flask-3.1.1.dist-info/entry_points.txt | 3 + .../licenses/LICENSE.txt | 28 + .venv/Lib/site-packages/flask/__init__.py | 61 + .venv/Lib/site-packages/flask/__main__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2548 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 256 bytes .../flask/__pycache__/app.cpython-312.pyc | Bin 0 -> 62433 bytes .../__pycache__/blueprints.cpython-312.pyc | Bin 0 -> 5015 bytes .../flask/__pycache__/cli.cpython-312.pyc | Bin 0 -> 43535 bytes .../flask/__pycache__/config.cpython-312.pyc | Bin 0 -> 16224 bytes .../flask/__pycache__/ctx.cpython-312.pyc | Bin 0 -> 19842 bytes .../__pycache__/debughelpers.cpython-312.pyc | Bin 0 -> 9147 bytes .../flask/__pycache__/globals.cpython-312.pyc | Bin 0 -> 1880 bytes .../flask/__pycache__/helpers.cpython-312.pyc | Bin 0 -> 25451 bytes .../flask/__pycache__/logging.cpython-312.pyc | Bin 0 -> 3285 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 17197 bytes .../flask/__pycache__/signals.cpython-312.pyc | Bin 0 -> 1237 bytes .../__pycache__/templating.cpython-312.pyc | Bin 0 -> 9922 bytes .../flask/__pycache__/testing.cpython-312.pyc | Bin 0 -> 13616 bytes .../flask/__pycache__/typing.cpython-312.pyc | Bin 0 -> 4145 bytes .../flask/__pycache__/views.cpython-312.pyc | Bin 0 -> 7021 bytes .../__pycache__/wrappers.cpython-312.pyc | Bin 0 -> 10068 bytes .venv/Lib/site-packages/flask/app.py | 1536 +++ .venv/Lib/site-packages/flask/blueprints.py | 128 + .venv/Lib/site-packages/flask/cli.py | 1135 +++ .venv/Lib/site-packages/flask/config.py | 367 + .venv/Lib/site-packages/flask/ctx.py | 449 + .venv/Lib/site-packages/flask/debughelpers.py | 178 + .venv/Lib/site-packages/flask/globals.py | 51 + .venv/Lib/site-packages/flask/helpers.py | 634 ++ .../Lib/site-packages/flask/json/__init__.py | 170 + .../json/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6708 bytes .../json/__pycache__/provider.cpython-312.pyc | Bin 0 -> 9275 bytes .../json/__pycache__/tag.cpython-312.pyc | Bin 0 -> 13970 bytes .../Lib/site-packages/flask/json/provider.py | 215 + .venv/Lib/site-packages/flask/json/tag.py | 327 + .venv/Lib/site-packages/flask/logging.py | 79 + .venv/Lib/site-packages/flask/py.typed | 0 .../Lib/site-packages/flask/sansio/README.md | 6 + .../sansio/__pycache__/app.cpython-312.pyc | Bin 0 -> 33702 bytes .../__pycache__/blueprints.cpython-312.pyc | Bin 0 -> 31199 bytes .../__pycache__/scaffold.cpython-312.pyc | Bin 0 -> 30239 bytes .venv/Lib/site-packages/flask/sansio/app.py | 964 ++ .../site-packages/flask/sansio/blueprints.py | 632 ++ .../site-packages/flask/sansio/scaffold.py | 792 ++ .venv/Lib/site-packages/flask/sessions.py | 399 + .venv/Lib/site-packages/flask/signals.py | 17 + .venv/Lib/site-packages/flask/templating.py | 219 + .venv/Lib/site-packages/flask/testing.py | 298 + .venv/Lib/site-packages/flask/typing.py | 93 + .venv/Lib/site-packages/flask/views.py | 191 + .venv/Lib/site-packages/flask/wrappers.py | 257 + .../h11-0.16.0.dist-info/INSTALLER | 1 + .../h11-0.16.0.dist-info/METADATA | 202 + .../site-packages/h11-0.16.0.dist-info/RECORD | 29 + .../site-packages/h11-0.16.0.dist-info/WHEEL | 5 + .../h11-0.16.0.dist-info/licenses/LICENSE.txt | 22 + .../h11-0.16.0.dist-info/top_level.txt | 1 + .venv/Lib/site-packages/h11/__init__.py | 62 + .../h11/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1091 bytes .../h11/__pycache__/_abnf.cpython-312.pyc | Bin 0 -> 1797 bytes .../__pycache__/_connection.cpython-312.pyc | Bin 0 -> 23147 bytes .../h11/__pycache__/_events.cpython-312.pyc | Bin 0 -> 13237 bytes .../h11/__pycache__/_headers.cpython-312.pyc | Bin 0 -> 7999 bytes .../h11/__pycache__/_readers.cpython-312.pyc | Bin 0 -> 9664 bytes .../_receivebuffer.cpython-312.pyc | Bin 0 -> 4715 bytes .../h11/__pycache__/_state.cpython-312.pyc | Bin 0 -> 8479 bytes .../h11/__pycache__/_util.cpython-312.pyc | Bin 0 -> 4735 bytes .../h11/__pycache__/_version.cpython-312.pyc | Bin 0 -> 229 bytes .../h11/__pycache__/_writers.cpython-312.pyc | Bin 0 -> 6301 bytes .venv/Lib/site-packages/h11/_abnf.py | 132 + .venv/Lib/site-packages/h11/_connection.py | 659 ++ .venv/Lib/site-packages/h11/_events.py | 369 + .venv/Lib/site-packages/h11/_headers.py | 282 + .venv/Lib/site-packages/h11/_readers.py | 250 + .venv/Lib/site-packages/h11/_receivebuffer.py | 153 + .venv/Lib/site-packages/h11/_state.py | 365 + .venv/Lib/site-packages/h11/_util.py | 135 + .venv/Lib/site-packages/h11/_version.py | 16 + .venv/Lib/site-packages/h11/_writers.py | 145 + .venv/Lib/site-packages/h11/py.typed | 1 + .../httpcore-1.0.9.dist-info/INSTALLER | 1 + .../httpcore-1.0.9.dist-info/METADATA | 625 ++ .../httpcore-1.0.9.dist-info/RECORD | 68 + .../httpcore-1.0.9.dist-info/WHEEL | 4 + .../licenses/LICENSE.md | 27 + .venv/Lib/site-packages/httpcore/__init__.py | 141 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3182 bytes .../httpcore/__pycache__/_api.cpython-312.pyc | Bin 0 -> 3768 bytes .../__pycache__/_exceptions.cpython-312.pyc | Bin 0 -> 3201 bytes .../__pycache__/_models.cpython-312.pyc | Bin 0 -> 23148 bytes .../httpcore/__pycache__/_ssl.cpython-312.pyc | Bin 0 -> 626 bytes .../_synchronization.cpython-312.pyc | Bin 0 -> 14169 bytes .../__pycache__/_trace.cpython-312.pyc | Bin 0 -> 5626 bytes .../__pycache__/_utils.cpython-312.pyc | Bin 0 -> 1306 bytes .venv/Lib/site-packages/httpcore/_api.py | 94 + .../site-packages/httpcore/_async/__init__.py | 39 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1639 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 11732 bytes .../connection_pool.cpython-312.pyc | Bin 0 -> 19614 bytes .../_async/__pycache__/http11.cpython-312.pyc | Bin 0 -> 20125 bytes .../_async/__pycache__/http2.cpython-312.pyc | Bin 0 -> 31191 bytes .../__pycache__/http_proxy.cpython-312.pyc | Bin 0 -> 17986 bytes .../__pycache__/interfaces.cpython-312.pyc | Bin 0 -> 5770 bytes .../__pycache__/socks_proxy.cpython-312.pyc | Bin 0 -> 16860 bytes .../httpcore/_async/connection.py | 222 + .../httpcore/_async/connection_pool.py | 420 + .../site-packages/httpcore/_async/http11.py | 379 + .../site-packages/httpcore/_async/http2.py | 592 ++ .../httpcore/_async/http_proxy.py | 367 + .../httpcore/_async/interfaces.py | 137 + .../httpcore/_async/socks_proxy.py | 341 + .../httpcore/_backends/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 215 bytes .../__pycache__/anyio.cpython-312.pyc | Bin 0 -> 8559 bytes .../__pycache__/auto.cpython-312.pyc | Bin 0 -> 2707 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 4909 bytes .../__pycache__/mock.cpython-312.pyc | Bin 0 -> 7173 bytes .../__pycache__/sync.cpython-312.pyc | Bin 0 -> 11380 bytes .../__pycache__/trio.cpython-312.pyc | Bin 0 -> 8898 bytes .../site-packages/httpcore/_backends/anyio.py | 146 + .../site-packages/httpcore/_backends/auto.py | 52 + .../site-packages/httpcore/_backends/base.py | 101 + .../site-packages/httpcore/_backends/mock.py | 143 + .../site-packages/httpcore/_backends/sync.py | 241 + .../site-packages/httpcore/_backends/trio.py | 159 + .../Lib/site-packages/httpcore/_exceptions.py | 81 + .venv/Lib/site-packages/httpcore/_models.py | 516 + .venv/Lib/site-packages/httpcore/_ssl.py | 9 + .../site-packages/httpcore/_sync/__init__.py | 39 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1593 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 10389 bytes .../connection_pool.cpython-312.pyc | Bin 0 -> 18691 bytes .../_sync/__pycache__/http11.cpython-312.pyc | Bin 0 -> 17712 bytes .../_sync/__pycache__/http2.cpython-312.pyc | Bin 0 -> 27323 bytes .../__pycache__/http_proxy.cpython-312.pyc | Bin 0 -> 17189 bytes .../__pycache__/interfaces.cpython-312.pyc | Bin 0 -> 5317 bytes .../__pycache__/socks_proxy.cpython-312.pyc | Bin 0 -> 15638 bytes .../httpcore/_sync/connection.py | 222 + .../httpcore/_sync/connection_pool.py | 420 + .../site-packages/httpcore/_sync/http11.py | 379 + .../Lib/site-packages/httpcore/_sync/http2.py | 592 ++ .../httpcore/_sync/http_proxy.py | 367 + .../httpcore/_sync/interfaces.py | 137 + .../httpcore/_sync/socks_proxy.py | 341 + .../httpcore/_synchronization.py | 318 + .venv/Lib/site-packages/httpcore/_trace.py | 107 + .venv/Lib/site-packages/httpcore/_utils.py | 37 + .venv/Lib/site-packages/httpcore/py.typed | 0 .../httpx-0.28.1.dist-info/INSTALLER | 1 + .../httpx-0.28.1.dist-info/METADATA | 203 + .../httpx-0.28.1.dist-info/RECORD | 55 + .../httpx-0.28.1.dist-info/REQUESTED | 0 .../httpx-0.28.1.dist-info/WHEEL | 4 + .../httpx-0.28.1.dist-info/entry_points.txt | 2 + .../licenses/LICENSE.md | 12 + .venv/Lib/site-packages/httpx/__init__.py | 105 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2136 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 331 bytes .../httpx/__pycache__/_api.cpython-312.pyc | Bin 0 -> 10351 bytes .../httpx/__pycache__/_auth.cpython-312.pyc | Bin 0 -> 15603 bytes .../httpx/__pycache__/_client.cpython-312.pyc | Bin 0 -> 64147 bytes .../httpx/__pycache__/_config.cpython-312.pyc | Bin 0 -> 10999 bytes .../__pycache__/_content.cpython-312.pyc | Bin 0 -> 10411 bytes .../__pycache__/_decoders.cpython-312.pyc | Bin 0 -> 16769 bytes .../__pycache__/_exceptions.cpython-312.pyc | Bin 0 -> 12101 bytes .../httpx/__pycache__/_main.cpython-312.pyc | Bin 0 -> 20606 bytes .../httpx/__pycache__/_models.cpython-312.pyc | Bin 0 -> 58266 bytes .../__pycache__/_multipart.cpython-312.pyc | Bin 0 -> 13585 bytes .../__pycache__/_status_codes.cpython-312.pyc | Bin 0 -> 7206 bytes .../httpx/__pycache__/_types.cpython-312.pyc | Bin 0 -> 3843 bytes .../__pycache__/_urlparse.cpython-312.pyc | Bin 0 -> 17531 bytes .../httpx/__pycache__/_urls.cpython-312.pyc | Bin 0 -> 27959 bytes .../httpx/__pycache__/_utils.cpython-312.pyc | Bin 0 -> 9373 bytes .venv/Lib/site-packages/httpx/__version__.py | 3 + .venv/Lib/site-packages/httpx/_api.py | 438 + .venv/Lib/site-packages/httpx/_auth.py | 348 + .venv/Lib/site-packages/httpx/_client.py | 2019 ++++ .venv/Lib/site-packages/httpx/_config.py | 248 + .venv/Lib/site-packages/httpx/_content.py | 240 + .venv/Lib/site-packages/httpx/_decoders.py | 393 + .venv/Lib/site-packages/httpx/_exceptions.py | 379 + .venv/Lib/site-packages/httpx/_main.py | 506 + .venv/Lib/site-packages/httpx/_models.py | 1277 +++ .venv/Lib/site-packages/httpx/_multipart.py | 300 + .../Lib/site-packages/httpx/_status_codes.py | 162 + .../httpx/_transports/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 460 bytes .../__pycache__/asgi.cpython-312.pyc | Bin 0 -> 7603 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 3890 bytes .../__pycache__/default.cpython-312.pyc | Bin 0 -> 17069 bytes .../__pycache__/mock.cpython-312.pyc | Bin 0 -> 1958 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 6822 bytes .../site-packages/httpx/_transports/asgi.py | 187 + .../site-packages/httpx/_transports/base.py | 86 + .../httpx/_transports/default.py | 406 + .../site-packages/httpx/_transports/mock.py | 43 + .../site-packages/httpx/_transports/wsgi.py | 149 + .venv/Lib/site-packages/httpx/_types.py | 114 + .venv/Lib/site-packages/httpx/_urlparse.py | 527 + .venv/Lib/site-packages/httpx/_urls.py | 641 ++ .venv/Lib/site-packages/httpx/_utils.py | 242 + .venv/Lib/site-packages/httpx/py.typed | 0 .../idna-3.10.dist-info/INSTALLER | 1 + .../idna-3.10.dist-info/LICENSE.md | 31 + .../idna-3.10.dist-info/METADATA | 250 + .../site-packages/idna-3.10.dist-info/RECORD | 22 + .../site-packages/idna-3.10.dist-info/WHEEL | 4 + .venv/Lib/site-packages/idna/__init__.py | 45 + .../idna/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 899 bytes .../idna/__pycache__/codec.cpython-312.pyc | Bin 0 -> 4989 bytes .../idna/__pycache__/compat.cpython-312.pyc | Bin 0 -> 903 bytes .../idna/__pycache__/core.cpython-312.pyc | Bin 0 -> 16134 bytes .../idna/__pycache__/idnadata.cpython-312.pyc | Bin 0 -> 99489 bytes .../__pycache__/intranges.cpython-312.pyc | Bin 0 -> 2646 bytes .../__pycache__/package_data.cpython-312.pyc | Bin 0 -> 230 bytes .../__pycache__/uts46data.cpython-312.pyc | Bin 0 -> 158859 bytes .venv/Lib/site-packages/idna/codec.py | 122 + .venv/Lib/site-packages/idna/compat.py | 15 + .venv/Lib/site-packages/idna/core.py | 437 + .venv/Lib/site-packages/idna/idnadata.py | 4243 ++++++++ .venv/Lib/site-packages/idna/intranges.py | 57 + .venv/Lib/site-packages/idna/package_data.py | 1 + .venv/Lib/site-packages/idna/py.typed | 0 .venv/Lib/site-packages/idna/uts46data.py | 8681 +++++++++++++++++ .../iniconfig-2.1.0.dist-info/INSTALLER | 1 + .../iniconfig-2.1.0.dist-info/METADATA | 81 + .../iniconfig-2.1.0.dist-info/RECORD | 14 + .../iniconfig-2.1.0.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .venv/Lib/site-packages/iniconfig/__init__.py | 216 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 8913 bytes .../__pycache__/_parse.cpython-312.pyc | Bin 0 -> 3343 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 667 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 1282 bytes .venv/Lib/site-packages/iniconfig/_parse.py | 82 + .venv/Lib/site-packages/iniconfig/_version.py | 21 + .../Lib/site-packages/iniconfig/exceptions.py | 20 + .venv/Lib/site-packages/iniconfig/py.typed | 0 .../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 -> 1647 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 1201 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2701 bytes .../__pycache__/exc.cpython-312.pyc | Bin 0 -> 3961 bytes .../__pycache__/serializer.cpython-312.pyc | Bin 0 -> 15429 bytes .../__pycache__/signer.cpython-312.pyc | Bin 0 -> 11306 bytes .../__pycache__/timed.cpython-312.pyc | Bin 0 -> 8750 bytes .../__pycache__/url_safe.cpython-312.pyc | Bin 0 -> 3551 bytes .venv/Lib/site-packages/itsdangerous/_json.py | 18 + .../site-packages/itsdangerous/encoding.py | 54 + .venv/Lib/site-packages/itsdangerous/exc.py | 106 + .venv/Lib/site-packages/itsdangerous/py.typed | 0 .../site-packages/itsdangerous/serializer.py | 406 + .../Lib/site-packages/itsdangerous/signer.py | 266 + .venv/Lib/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 + .venv/Lib/site-packages/jinja2/__init__.py | 38 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1672 bytes .../__pycache__/_identifier.cpython-312.pyc | Bin 0 -> 2153 bytes .../__pycache__/async_utils.cpython-312.pyc | Bin 0 -> 4993 bytes .../__pycache__/bccache.cpython-312.pyc | Bin 0 -> 19344 bytes .../__pycache__/compiler.cpython-312.pyc | Bin 0 -> 103911 bytes .../__pycache__/constants.cpython-312.pyc | Bin 0 -> 1575 bytes .../jinja2/__pycache__/debug.cpython-312.pyc | Bin 0 -> 6585 bytes .../__pycache__/defaults.cpython-312.pyc | Bin 0 -> 1625 bytes .../__pycache__/environment.cpython-312.pyc | Bin 0 -> 76654 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7734 bytes .../jinja2/__pycache__/ext.cpython-312.pyc | Bin 0 -> 41896 bytes .../__pycache__/filters.cpython-312.pyc | Bin 0 -> 72288 bytes .../__pycache__/idtracking.cpython-312.pyc | Bin 0 -> 19139 bytes .../jinja2/__pycache__/lexer.cpython-312.pyc | Bin 0 -> 32084 bytes .../__pycache__/loaders.cpython-312.pyc | Bin 0 -> 32294 bytes .../jinja2/__pycache__/meta.cpython-312.pyc | Bin 0 -> 5489 bytes .../__pycache__/nativetypes.cpython-312.pyc | Bin 0 -> 7038 bytes .../jinja2/__pycache__/nodes.cpython-312.pyc | Bin 0 -> 58238 bytes .../__pycache__/optimizer.cpython-312.pyc | Bin 0 -> 2708 bytes .../jinja2/__pycache__/parser.cpython-312.pyc | Bin 0 -> 61199 bytes .../__pycache__/runtime.cpython-312.pyc | Bin 0 -> 48903 bytes .../__pycache__/sandbox.cpython-312.pyc | Bin 0 -> 18116 bytes .../jinja2/__pycache__/tests.cpython-312.pyc | Bin 0 -> 9069 bytes .../jinja2/__pycache__/utils.cpython-312.pyc | Bin 0 -> 34817 bytes .../__pycache__/visitor.cpython-312.pyc | Bin 0 -> 5372 bytes .venv/Lib/site-packages/jinja2/_identifier.py | 6 + .venv/Lib/site-packages/jinja2/async_utils.py | 99 + .venv/Lib/site-packages/jinja2/bccache.py | 408 + .venv/Lib/site-packages/jinja2/compiler.py | 1998 ++++ .venv/Lib/site-packages/jinja2/constants.py | 20 + .venv/Lib/site-packages/jinja2/debug.py | 191 + .venv/Lib/site-packages/jinja2/defaults.py | 48 + .venv/Lib/site-packages/jinja2/environment.py | 1672 ++++ .venv/Lib/site-packages/jinja2/exceptions.py | 166 + .venv/Lib/site-packages/jinja2/ext.py | 870 ++ .venv/Lib/site-packages/jinja2/filters.py | 1873 ++++ .venv/Lib/site-packages/jinja2/idtracking.py | 318 + .venv/Lib/site-packages/jinja2/lexer.py | 868 ++ .venv/Lib/site-packages/jinja2/loaders.py | 693 ++ .venv/Lib/site-packages/jinja2/meta.py | 112 + .venv/Lib/site-packages/jinja2/nativetypes.py | 130 + .venv/Lib/site-packages/jinja2/nodes.py | 1206 +++ .venv/Lib/site-packages/jinja2/optimizer.py | 48 + .venv/Lib/site-packages/jinja2/parser.py | 1049 ++ .venv/Lib/site-packages/jinja2/py.typed | 0 .venv/Lib/site-packages/jinja2/runtime.py | 1062 ++ .venv/Lib/site-packages/jinja2/sandbox.py | 436 + .venv/Lib/site-packages/jinja2/tests.py | 256 + .venv/Lib/site-packages/jinja2/utils.py | 766 ++ .venv/Lib/site-packages/jinja2/visitor.py | 92 + .../Lib/site-packages/markupsafe/__init__.py | 395 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 20972 bytes .../__pycache__/_native.cpython-312.pyc | Bin 0 -> 634 bytes .venv/Lib/site-packages/markupsafe/_native.py | 8 + .../Lib/site-packages/markupsafe/_speedups.c | 204 + .../markupsafe/_speedups.cp312-win_amd64.pyd | Bin 0 -> 13312 bytes .../site-packages/markupsafe/_speedups.pyi | 1 + .venv/Lib/site-packages/markupsafe/py.typed | 0 .../packaging-25.0.dist-info/INSTALLER | 1 + .../packaging-25.0.dist-info/METADATA | 105 + .../packaging-25.0.dist-info/RECORD | 40 + .../packaging-25.0.dist-info/WHEEL | 4 + .../packaging-25.0.dist-info/licenses/LICENSE | 3 + .../licenses/LICENSE.APACHE | 177 + .../licenses/LICENSE.BSD | 23 + .venv/Lib/site-packages/packaging/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 571 bytes .../__pycache__/_elffile.cpython-312.pyc | Bin 0 -> 5029 bytes .../__pycache__/_manylinux.cpython-312.pyc | Bin 0 -> 9719 bytes .../__pycache__/_musllinux.cpython-312.pyc | Bin 0 -> 4567 bytes .../__pycache__/_parser.cpython-312.pyc | Bin 0 -> 13996 bytes .../__pycache__/_structures.cpython-312.pyc | Bin 0 -> 3254 bytes .../__pycache__/_tokenizer.cpython-312.pyc | Bin 0 -> 7965 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 12779 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 27221 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 4423 bytes .../__pycache__/specifiers.cpython-312.pyc | Bin 0 -> 39028 bytes .../__pycache__/tags.cpython-312.pyc | Bin 0 -> 24686 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 6648 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 20487 bytes .venv/Lib/site-packages/packaging/_elffile.py | 109 + .../Lib/site-packages/packaging/_manylinux.py | 262 + .../Lib/site-packages/packaging/_musllinux.py | 85 + .venv/Lib/site-packages/packaging/_parser.py | 353 + .../site-packages/packaging/_structures.py | 61 + .../Lib/site-packages/packaging/_tokenizer.py | 195 + .../packaging/licenses/__init__.py | 145 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4121 bytes .../__pycache__/_spdx.cpython-312.pyc | Bin 0 -> 47377 bytes .../site-packages/packaging/licenses/_spdx.py | 759 ++ .venv/Lib/site-packages/packaging/markers.py | 362 + .venv/Lib/site-packages/packaging/metadata.py | 862 ++ .venv/Lib/site-packages/packaging/py.typed | 0 .../site-packages/packaging/requirements.py | 91 + .../Lib/site-packages/packaging/specifiers.py | 1019 ++ .venv/Lib/site-packages/packaging/tags.py | 656 ++ .venv/Lib/site-packages/packaging/utils.py | 163 + .venv/Lib/site-packages/packaging/version.py | 582 ++ .../pip-24.3.1.dist-info/AUTHORS.txt | 799 ++ .../pip-24.3.1.dist-info/INSTALLER | 1 + .../pip-24.3.1.dist-info/LICENSE.txt | 20 + .../pip-24.3.1.dist-info/METADATA | 90 + .../site-packages/pip-24.3.1.dist-info/RECORD | 853 ++ .../pip-24.3.1.dist-info/REQUESTED | 0 .../site-packages/pip-24.3.1.dist-info/WHEEL | 5 + .../pip-24.3.1.dist-info/entry_points.txt | 3 + .../pip-24.3.1.dist-info/top_level.txt | 1 + .venv/Lib/site-packages/pip/__init__.py | 13 + .venv/Lib/site-packages/pip/__main__.py | 24 + .venv/Lib/site-packages/pip/__pip-runner__.py | 50 + .../pip/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 711 bytes .../pip/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 865 bytes .../__pip-runner__.cpython-312.pyc | Bin 0 -> 2229 bytes .../site-packages/pip/_internal/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 811 bytes .../__pycache__/build_env.cpython-312.pyc | Bin 0 -> 14523 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 12691 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 17656 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 36869 bytes .../__pycache__/main.cpython-312.pyc | Bin 0 -> 694 bytes .../__pycache__/pyproject.cpython-312.pyc | Bin 0 -> 5142 bytes .../self_outdated_check.cpython-312.pyc | Bin 0 -> 10232 bytes .../__pycache__/wheel_builder.cpython-312.pyc | Bin 0 -> 13639 bytes .../site-packages/pip/_internal/build_env.py | 319 + .../Lib/site-packages/pip/_internal/cache.py | 290 + .../pip/_internal/cli/__init__.py | 4 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 302 bytes .../autocompletion.cpython-312.pyc | Bin 0 -> 8630 bytes .../__pycache__/base_command.cpython-312.pyc | Bin 0 -> 10218 bytes .../__pycache__/cmdoptions.cpython-312.pyc | Bin 0 -> 30419 bytes .../command_context.cpython-312.pyc | Bin 0 -> 1798 bytes .../__pycache__/index_command.cpython-312.pyc | Bin 0 -> 7148 bytes .../cli/__pycache__/main.cpython-312.pyc | Bin 0 -> 2324 bytes .../__pycache__/main_parser.cpython-312.pyc | Bin 0 -> 4930 bytes .../cli/__pycache__/parser.cpython-312.pyc | Bin 0 -> 15064 bytes .../__pycache__/progress_bars.cpython-312.pyc | Bin 0 -> 3868 bytes .../__pycache__/req_command.cpython-312.pyc | Bin 0 -> 12265 bytes .../cli/__pycache__/spinners.cpython-312.pyc | Bin 0 -> 7857 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 399 bytes .../pip/_internal/cli/autocompletion.py | 176 + .../pip/_internal/cli/base_command.py | 231 + .../pip/_internal/cli/cmdoptions.py | 1075 ++ .../pip/_internal/cli/command_context.py | 27 + .../pip/_internal/cli/index_command.py | 170 + .../site-packages/pip/_internal/cli/main.py | 80 + .../pip/_internal/cli/main_parser.py | 134 + .../site-packages/pip/_internal/cli/parser.py | 294 + .../pip/_internal/cli/progress_bars.py | 94 + .../pip/_internal/cli/req_command.py | 329 + .../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 -> 4026 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 9725 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 2616 bytes .../__pycache__/completion.cpython-312.pyc | Bin 0 -> 5217 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 13187 bytes .../__pycache__/debug.cpython-312.pyc | Bin 0 -> 10092 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 7527 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 4409 bytes .../commands/__pycache__/hash.cpython-312.pyc | Bin 0 -> 2992 bytes .../commands/__pycache__/help.cpython-312.pyc | Bin 0 -> 1697 bytes .../__pycache__/index.cpython-312.pyc | Bin 0 -> 6695 bytes .../__pycache__/inspect.cpython-312.pyc | Bin 0 -> 4004 bytes .../__pycache__/install.cpython-312.pyc | Bin 0 -> 29138 bytes .../commands/__pycache__/list.cpython-312.pyc | Bin 0 -> 15781 bytes .../__pycache__/search.cpython-312.pyc | Bin 0 -> 7542 bytes .../commands/__pycache__/show.cpython-312.pyc | Bin 0 -> 10501 bytes .../__pycache__/uninstall.cpython-312.pyc | Bin 0 -> 4733 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 8889 bytes .../pip/_internal/commands/cache.py | 225 + .../pip/_internal/commands/check.py | 67 + .../pip/_internal/commands/completion.py | 130 + .../pip/_internal/commands/configuration.py | 280 + .../pip/_internal/commands/debug.py | 201 + .../pip/_internal/commands/download.py | 146 + .../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 | 783 ++ .../pip/_internal/commands/list.py | 375 + .../pip/_internal/commands/search.py | 172 + .../pip/_internal/commands/show.py | 217 + .../pip/_internal/commands/uninstall.py | 114 + .../pip/_internal/commands/wheel.py | 182 + .../pip/_internal/configuration.py | 383 + .../pip/_internal/distributions/__init__.py | 21 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 965 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 2917 bytes .../__pycache__/installed.cpython-312.pyc | Bin 0 -> 1724 bytes .../__pycache__/sdist.cpython-312.pyc | Bin 0 -> 8451 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 2305 bytes .../pip/_internal/distributions/base.py | 53 + .../pip/_internal/distributions/installed.py | 29 + .../pip/_internal/distributions/sdist.py | 158 + .../pip/_internal/distributions/wheel.py | 42 + .../site-packages/pip/_internal/exceptions.py | 809 ++ .../pip/_internal/index/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 256 bytes .../__pycache__/collector.cpython-312.pyc | Bin 0 -> 21641 bytes .../package_finder.cpython-312.pyc | Bin 0 -> 40672 bytes .../index/__pycache__/sources.cpython-312.pyc | Bin 0 -> 12548 bytes .../pip/_internal/index/collector.py | 494 + .../pip/_internal/index/package_finder.py | 1020 ++ .../pip/_internal/index/sources.py | 284 + .../pip/_internal/locations/__init__.py | 456 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 16464 bytes .../__pycache__/_distutils.cpython-312.pyc | Bin 0 -> 6815 bytes .../__pycache__/_sysconfig.cpython-312.pyc | Bin 0 -> 8052 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 3805 bytes .../pip/_internal/locations/_distutils.py | 172 + .../pip/_internal/locations/_sysconfig.py | 214 + .../pip/_internal/locations/base.py | 81 + .venv/Lib/site-packages/pip/_internal/main.py | 12 + .../pip/_internal/metadata/__init__.py | 128 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5893 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 2950 bytes .../metadata/__pycache__/base.cpython-312.pyc | Bin 0 -> 35222 bytes .../__pycache__/pkg_resources.cpython-312.pyc | Bin 0 -> 16108 bytes .../pip/_internal/metadata/_json.py | 84 + .../pip/_internal/metadata/base.py | 688 ++ .../_internal/metadata/importlib/__init__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 382 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 4515 bytes .../__pycache__/_dists.cpython-312.pyc | Bin 0 -> 12589 bytes .../__pycache__/_envs.cpython-312.pyc | Bin 0 -> 11103 bytes .../_internal/metadata/importlib/_compat.py | 85 + .../_internal/metadata/importlib/_dists.py | 221 + .../pip/_internal/metadata/importlib/_envs.py | 189 + .../pip/_internal/metadata/pkg_resources.py | 301 + .../pip/_internal/models/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 290 bytes .../__pycache__/candidate.cpython-312.pyc | Bin 0 -> 1628 bytes .../__pycache__/direct_url.cpython-312.pyc | Bin 0 -> 10868 bytes .../format_control.cpython-312.pyc | Bin 0 -> 4247 bytes .../models/__pycache__/index.cpython-312.pyc | Bin 0 -> 1718 bytes .../installation_report.cpython-312.pyc | Bin 0 -> 2301 bytes .../models/__pycache__/link.cpython-312.pyc | Bin 0 -> 26641 bytes .../models/__pycache__/scheme.cpython-312.pyc | Bin 0 -> 1047 bytes .../__pycache__/search_scope.cpython-312.pyc | Bin 0 -> 5011 bytes .../selection_prefs.cpython-312.pyc | Bin 0 -> 1875 bytes .../__pycache__/target_python.cpython-312.pyc | Bin 0 -> 4977 bytes .../models/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 6576 bytes .../pip/_internal/models/candidate.py | 25 + .../pip/_internal/models/direct_url.py | 224 + .../pip/_internal/models/format_control.py | 78 + .../pip/_internal/models/index.py | 28 + .../_internal/models/installation_report.py | 56 + .../pip/_internal/models/link.py | 590 ++ .../pip/_internal/models/scheme.py | 25 + .../pip/_internal/models/search_scope.py | 127 + .../pip/_internal/models/selection_prefs.py | 53 + .../pip/_internal/models/target_python.py | 121 + .../pip/_internal/models/wheel.py | 118 + .../pip/_internal/network/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 278 bytes .../network/__pycache__/auth.cpython-312.pyc | Bin 0 -> 22123 bytes .../network/__pycache__/cache.cpython-312.pyc | Bin 0 -> 6475 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 8503 bytes .../__pycache__/lazy_wheel.cpython-312.pyc | Bin 0 -> 11631 bytes .../__pycache__/session.cpython-312.pyc | Bin 0 -> 18898 bytes .../network/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2279 bytes .../__pycache__/xmlrpc.cpython-312.pyc | Bin 0 -> 2973 bytes .../pip/_internal/network/auth.py | 566 ++ .../pip/_internal/network/cache.py | 106 + .../pip/_internal/network/download.py | 187 + .../pip/_internal/network/lazy_wheel.py | 210 + .../pip/_internal/network/session.py | 522 + .../pip/_internal/network/utils.py | 98 + .../pip/_internal/network/xmlrpc.py | 62 + .../pip/_internal/operations/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 221 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 7128 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 10152 bytes .../__pycache__/prepare.cpython-312.pyc | Bin 0 -> 25796 bytes .../_internal/operations/build/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 227 bytes .../__pycache__/build_tracker.cpython-312.pyc | Bin 0 -> 7691 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 1881 bytes .../metadata_editable.cpython-312.pyc | Bin 0 -> 1915 bytes .../metadata_legacy.cpython-312.pyc | Bin 0 -> 3035 bytes .../build/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 1701 bytes .../wheel_editable.cpython-312.pyc | Bin 0 -> 2040 bytes .../__pycache__/wheel_legacy.cpython-312.pyc | Bin 0 -> 3870 bytes .../operations/build/build_tracker.py | 138 + .../_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 | 181 + .../pip/_internal/operations/freeze.py | 258 + .../_internal/operations/install/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 290 bytes .../editable_legacy.cpython-312.pyc | Bin 0 -> 1822 bytes .../install/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 34127 bytes .../operations/install/editable_legacy.py | 47 + .../pip/_internal/operations/install/wheel.py | 741 ++ .../pip/_internal/operations/prepare.py | 732 ++ .../site-packages/pip/_internal/pyproject.py | 185 + .../pip/_internal/req/__init__.py | 90 + .../req/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3469 bytes .../__pycache__/constructors.cpython-312.pyc | Bin 0 -> 21279 bytes .../req/__pycache__/req_file.cpython-312.pyc | Bin 0 -> 22147 bytes .../__pycache__/req_install.cpython-312.pyc | Bin 0 -> 38499 bytes .../req/__pycache__/req_set.cpython-312.pyc | Bin 0 -> 5507 bytes .../__pycache__/req_uninstall.cpython-312.pyc | Bin 0 -> 32118 bytes .../pip/_internal/req/constructors.py | 560 ++ .../pip/_internal/req/req_file.py | 574 ++ .../pip/_internal/req/req_install.py | 934 ++ .../pip/_internal/req/req_set.py | 82 + .../pip/_internal/req/req_uninstall.py | 633 ++ .../pip/_internal/resolution/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 221 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 1209 bytes .../pip/_internal/resolution/base.py | 20 + .../_internal/resolution/legacy/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 228 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 22603 bytes .../_internal/resolution/legacy/resolver.py | 597 ++ .../resolution/resolvelib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 232 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 8173 bytes .../__pycache__/candidates.cpython-312.pyc | Bin 0 -> 29432 bytes .../__pycache__/factory.cpython-312.pyc | Bin 0 -> 32543 bytes .../found_candidates.cpython-312.pyc | Bin 0 -> 6817 bytes .../__pycache__/provider.cpython-312.pyc | Bin 0 -> 10547 bytes .../__pycache__/reporter.cpython-312.pyc | Bin 0 -> 5064 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 15380 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 12338 bytes .../_internal/resolution/resolvelib/base.py | 139 + .../resolution/resolvelib/candidates.py | 574 ++ .../resolution/resolvelib/factory.py | 823 ++ .../resolution/resolvelib/found_candidates.py | 174 + .../resolution/resolvelib/provider.py | 258 + .../resolution/resolvelib/reporter.py | 81 + .../resolution/resolvelib/requirements.py | 245 + .../resolution/resolvelib/resolver.py | 317 + .../pip/_internal/self_outdated_check.py | 244 + .../pip/_internal/utils/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 216 bytes .../__pycache__/_jaraco_text.cpython-312.pyc | Bin 0 -> 4551 bytes .../utils/__pycache__/_log.cpython-312.pyc | Bin 0 -> 1887 bytes .../utils/__pycache__/appdirs.cpython-312.pyc | Bin 0 -> 2431 bytes .../utils/__pycache__/compat.cpython-312.pyc | Bin 0 -> 2928 bytes .../compatibility_tags.cpython-312.pyc | Bin 0 -> 6371 bytes .../__pycache__/datetime.cpython-312.pyc | Bin 0 -> 705 bytes .../__pycache__/deprecation.cpython-312.pyc | Bin 0 -> 4212 bytes .../direct_url_helpers.cpython-312.pyc | Bin 0 -> 3557 bytes .../__pycache__/egg_link.cpython-312.pyc | Bin 0 -> 3227 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2169 bytes .../__pycache__/entrypoints.cpython-312.pyc | Bin 0 -> 4014 bytes .../__pycache__/filesystem.cpython-312.pyc | Bin 0 -> 7350 bytes .../__pycache__/filetypes.cpython-312.pyc | Bin 0 -> 1185 bytes .../utils/__pycache__/glibc.cpython-312.pyc | Bin 0 -> 2440 bytes .../utils/__pycache__/hashes.cpython-312.pyc | Bin 0 -> 7624 bytes .../utils/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13579 bytes .../utils/__pycache__/misc.cpython-312.pyc | Bin 0 -> 33472 bytes .../__pycache__/packaging.cpython-312.pyc | Bin 0 -> 2604 bytes .../utils/__pycache__/retry.cpython-312.pyc | Bin 0 -> 2129 bytes .../setuptools_build.cpython-312.pyc | Bin 0 -> 4571 bytes .../__pycache__/subprocess.cpython-312.pyc | Bin 0 -> 8660 bytes .../__pycache__/temp_dir.cpython-312.pyc | Bin 0 -> 12045 bytes .../__pycache__/unpacking.cpython-312.pyc | Bin 0 -> 13519 bytes .../utils/__pycache__/urls.cpython-312.pyc | Bin 0 -> 2098 bytes .../__pycache__/virtualenv.cpython-312.pyc | Bin 0 -> 4487 bytes .../utils/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 5924 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 | 79 + .../pip/_internal/utils/compatibility_tags.py | 188 + .../pip/_internal/utils/datetime.py | 11 + .../pip/_internal/utils/deprecation.py | 124 + .../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 | 149 + .../pip/_internal/utils/filetypes.py | 27 + .../pip/_internal/utils/glibc.py | 101 + .../pip/_internal/utils/hashes.py | 147 + .../pip/_internal/utils/logging.py | 347 + .../site-packages/pip/_internal/utils/misc.py | 772 ++ .../pip/_internal/utils/packaging.py | 57 + .../pip/_internal/utils/retry.py | 42 + .../pip/_internal/utils/setuptools_build.py | 146 + .../pip/_internal/utils/subprocess.py | 245 + .../pip/_internal/utils/temp_dir.py | 296 + .../pip/_internal/utils/unpacking.py | 337 + .../site-packages/pip/_internal/utils/urls.py | 55 + .../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 -> 555 bytes .../vcs/__pycache__/bazaar.cpython-312.pyc | Bin 0 -> 5076 bytes .../vcs/__pycache__/git.cpython-312.pyc | Bin 0 -> 19041 bytes .../vcs/__pycache__/mercurial.cpython-312.pyc | Bin 0 -> 7629 bytes .../__pycache__/subversion.cpython-312.pyc | Bin 0 -> 12548 bytes .../versioncontrol.cpython-312.pyc | Bin 0 -> 29021 bytes .../site-packages/pip/_internal/vcs/bazaar.py | 112 + .../site-packages/pip/_internal/vcs/git.py | 527 + .../pip/_internal/vcs/mercurial.py | 163 + .../pip/_internal/vcs/subversion.py | 324 + .../pip/_internal/vcs/versioncontrol.py | 688 ++ .../pip/_internal/wheel_builder.py | 354 + .../Lib/site-packages/pip/_vendor/__init__.py | 116 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4574 bytes .../typing_extensions.cpython-312.pyc | Bin 0 -> 139476 bytes .../pip/_vendor/cachecontrol/__init__.py | 28 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 927 bytes .../__pycache__/_cmd.cpython-312.pyc | Bin 0 -> 2671 bytes .../__pycache__/adapter.cpython-312.pyc | Bin 0 -> 6489 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 3812 bytes .../__pycache__/controller.cpython-312.pyc | Bin 0 -> 16249 bytes .../__pycache__/filewrapper.cpython-312.pyc | Bin 0 -> 4372 bytes .../__pycache__/heuristics.cpython-312.pyc | Bin 0 -> 6719 bytes .../__pycache__/serialize.cpython-312.pyc | Bin 0 -> 5286 bytes .../__pycache__/wrapper.cpython-312.pyc | Bin 0 -> 1699 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 -> 460 bytes .../__pycache__/file_cache.cpython-312.pyc | Bin 0 -> 7788 bytes .../__pycache__/redis_cache.cpython-312.pyc | Bin 0 -> 2758 bytes .../_vendor/cachecontrol/caches/file_cache.py | 182 + .../cachecontrol/caches/redis_cache.py | 48 + .../pip/_vendor/cachecontrol/controller.py | 499 + .../pip/_vendor/cachecontrol/filewrapper.py | 119 + .../pip/_vendor/cachecontrol/heuristics.py | 154 + .../pip/_vendor/cachecontrol/py.typed | 0 .../pip/_vendor/cachecontrol/serialize.py | 146 + .../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 -> 343 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 670 bytes .../certifi/__pycache__/core.cpython-312.pyc | Bin 0 -> 3236 bytes .../site-packages/pip/_vendor/certifi/core.py | 114 + .../pip/_vendor/certifi/py.typed | 0 .../pip/_vendor/distlib/__init__.py | 33 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1294 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 45551 bytes .../__pycache__/database.cpython-312.pyc | Bin 0 -> 65606 bytes .../distlib/__pycache__/index.cpython-312.pyc | Bin 0 -> 24341 bytes .../__pycache__/locators.cpython-312.pyc | Bin 0 -> 59886 bytes .../__pycache__/manifest.cpython-312.pyc | Bin 0 -> 15101 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 7679 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 41585 bytes .../__pycache__/resources.cpython-312.pyc | Bin 0 -> 17337 bytes .../__pycache__/scripts.cpython-312.pyc | Bin 0 -> 19778 bytes .../distlib/__pycache__/util.cpython-312.pyc | Bin 0 -> 88060 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 30369 bytes .../distlib/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 52570 bytes .../pip/_vendor/distlib/compat.py | 1137 +++ .../pip/_vendor/distlib/database.py | 1329 +++ .../pip/_vendor/distlib/index.py | 508 + .../pip/_vendor/distlib/locators.py | 1295 +++ .../pip/_vendor/distlib/manifest.py | 384 + .../pip/_vendor/distlib/markers.py | 162 + .../pip/_vendor/distlib/metadata.py | 1031 ++ .../pip/_vendor/distlib/resources.py | 358 + .../pip/_vendor/distlib/scripts.py | 447 + .../site-packages/pip/_vendor/distlib/t32.exe | Bin 0 -> 97792 bytes .../pip/_vendor/distlib/t64-arm.exe | Bin 0 -> 182784 bytes .../site-packages/pip/_vendor/distlib/t64.exe | Bin 0 -> 108032 bytes .../site-packages/pip/_vendor/distlib/util.py | 1984 ++++ .../pip/_vendor/distlib/version.py | 750 ++ .../site-packages/pip/_vendor/distlib/w32.exe | Bin 0 -> 91648 bytes .../pip/_vendor/distlib/w64-arm.exe | Bin 0 -> 168448 bytes .../site-packages/pip/_vendor/distlib/w64.exe | Bin 0 -> 101888 bytes .../pip/_vendor/distlib/wheel.py | 1100 +++ .../pip/_vendor/distro/__init__.py | 54 + .../pip/_vendor/distro/__main__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 985 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 317 bytes .../distro/__pycache__/distro.cpython-312.pyc | Bin 0 -> 53817 bytes .../pip/_vendor/distro/distro.py | 1403 +++ .../site-packages/pip/_vendor/distro/py.typed | 0 .../pip/_vendor/idna/__init__.py | 44 + .../idna/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 906 bytes .../idna/__pycache__/codec.cpython-312.pyc | Bin 0 -> 5001 bytes .../idna/__pycache__/compat.cpython-312.pyc | Bin 0 -> 912 bytes .../idna/__pycache__/core.cpython-312.pyc | Bin 0 -> 15816 bytes .../idna/__pycache__/idnadata.cpython-312.pyc | Bin 0 -> 99501 bytes .../__pycache__/intranges.cpython-312.pyc | Bin 0 -> 2658 bytes .../__pycache__/package_data.cpython-312.pyc | Bin 0 -> 241 bytes .../__pycache__/uts46data.cpython-312.pyc | Bin 0 -> 158873 bytes .../site-packages/pip/_vendor/idna/codec.py | 118 + .../site-packages/pip/_vendor/idna/compat.py | 13 + .../site-packages/pip/_vendor/idna/core.py | 395 + .../pip/_vendor/idna/idnadata.py | 4245 ++++++++ .../pip/_vendor/idna/intranges.py | 54 + .../pip/_vendor/idna/package_data.py | 2 + .../site-packages/pip/_vendor/idna/py.typed | 0 .../pip/_vendor/idna/uts46data.py | 8598 ++++++++++++++++ .../pip/_vendor/msgpack/__init__.py | 55 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1766 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 2050 bytes .../msgpack/__pycache__/ext.cpython-312.pyc | Bin 0 -> 8195 bytes .../__pycache__/fallback.cpython-312.pyc | Bin 0 -> 42068 bytes .../pip/_vendor/msgpack/exceptions.py | 48 + .../site-packages/pip/_vendor/msgpack/ext.py | 168 + .../pip/_vendor/msgpack/fallback.py | 951 ++ .../pip/_vendor/packaging/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 583 bytes .../__pycache__/_elffile.cpython-312.pyc | Bin 0 -> 4992 bytes .../__pycache__/_manylinux.cpython-312.pyc | Bin 0 -> 9710 bytes .../__pycache__/_musllinux.cpython-312.pyc | Bin 0 -> 4579 bytes .../__pycache__/_parser.cpython-312.pyc | Bin 0 -> 14010 bytes .../__pycache__/_structures.cpython-312.pyc | Bin 0 -> 3266 bytes .../__pycache__/_tokenizer.cpython-312.pyc | Bin 0 -> 7940 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 11036 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 24978 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 4435 bytes .../__pycache__/specifiers.cpython-312.pyc | Bin 0 -> 38764 bytes .../__pycache__/tags.cpython-312.pyc | Bin 0 -> 23244 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 7366 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 19532 bytes .../pip/_vendor/packaging/_elffile.py | 110 + .../pip/_vendor/packaging/_manylinux.py | 262 + .../pip/_vendor/packaging/_musllinux.py | 85 + .../pip/_vendor/packaging/_parser.py | 354 + .../pip/_vendor/packaging/_structures.py | 61 + .../pip/_vendor/packaging/_tokenizer.py | 194 + .../pip/_vendor/packaging/markers.py | 325 + .../pip/_vendor/packaging/metadata.py | 804 ++ .../pip/_vendor/packaging/py.typed | 0 .../pip/_vendor/packaging/requirements.py | 91 + .../pip/_vendor/packaging/specifiers.py | 1009 ++ .../pip/_vendor/packaging/tags.py | 627 ++ .../pip/_vendor/packaging/utils.py | 174 + .../pip/_vendor/packaging/version.py | 563 ++ .../pip/_vendor/pkg_resources/__init__.py | 3676 +++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 161286 bytes .../pip/_vendor/platformdirs/__init__.py | 627 ++ .../pip/_vendor/platformdirs/__main__.py | 55 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 19854 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 1973 bytes .../__pycache__/android.cpython-312.pyc | Bin 0 -> 10722 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 12936 bytes .../__pycache__/macos.cpython-312.pyc | Bin 0 -> 8032 bytes .../__pycache__/unix.cpython-312.pyc | Bin 0 -> 15062 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 622 bytes .../__pycache__/windows.cpython-312.pyc | Bin 0 -> 13699 bytes .../pip/_vendor/platformdirs/android.py | 249 + .../pip/_vendor/platformdirs/api.py | 292 + .../pip/_vendor/platformdirs/macos.py | 130 + .../pip/_vendor/platformdirs/py.typed | 0 .../pip/_vendor/platformdirs/unix.py | 275 + .../pip/_vendor/platformdirs/version.py | 16 + .../pip/_vendor/platformdirs/windows.py | 272 + .../pip/_vendor/pygments/__init__.py | 82 + .../pip/_vendor/pygments/__main__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3514 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 760 bytes .../__pycache__/cmdline.cpython-312.pyc | Bin 0 -> 26610 bytes .../__pycache__/console.cpython-312.pyc | Bin 0 -> 2654 bytes .../__pycache__/filter.cpython-312.pyc | Bin 0 -> 3247 bytes .../__pycache__/formatter.cpython-312.pyc | Bin 0 -> 4746 bytes .../__pycache__/lexer.cpython-312.pyc | Bin 0 -> 38387 bytes .../__pycache__/modeline.cpython-312.pyc | Bin 0 -> 1585 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 2634 bytes .../__pycache__/regexopt.cpython-312.pyc | Bin 0 -> 4103 bytes .../__pycache__/scanner.cpython-312.pyc | Bin 0 -> 4782 bytes .../__pycache__/sphinxext.cpython-312.pyc | Bin 0 -> 12124 bytes .../__pycache__/style.cpython-312.pyc | Bin 0 -> 6719 bytes .../__pycache__/token.cpython-312.pyc | Bin 0 -> 8215 bytes .../__pycache__/unistring.cpython-312.pyc | Bin 0 -> 32998 bytes .../pygments/__pycache__/util.cpython-312.pyc | Bin 0 -> 14095 bytes .../pip/_vendor/pygments/cmdline.py | 668 ++ .../pip/_vendor/pygments/console.py | 70 + .../pip/_vendor/pygments/filter.py | 70 + .../pip/_vendor/pygments/filters/__init__.py | 940 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 37937 bytes .../pip/_vendor/pygments/formatter.py | 129 + .../_vendor/pygments/formatters/__init__.py | 157 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6928 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 4241 bytes .../__pycache__/bbcode.cpython-312.pyc | Bin 0 -> 4248 bytes .../__pycache__/groff.cpython-312.pyc | Bin 0 -> 7319 bytes .../__pycache__/html.cpython-312.pyc | Bin 0 -> 41052 bytes .../__pycache__/img.cpython-312.pyc | Bin 0 -> 28574 bytes .../__pycache__/irc.cpython-312.pyc | Bin 0 -> 6081 bytes .../__pycache__/latex.cpython-312.pyc | Bin 0 -> 20151 bytes .../__pycache__/other.cpython-312.pyc | Bin 0 -> 6903 bytes .../__pycache__/pangomarkup.cpython-312.pyc | Bin 0 -> 2984 bytes .../__pycache__/rtf.cpython-312.pyc | Bin 0 -> 13799 bytes .../__pycache__/svg.cpython-312.pyc | Bin 0 -> 9165 bytes .../__pycache__/terminal.cpython-312.pyc | Bin 0 -> 5845 bytes .../__pycache__/terminal256.cpython-312.pyc | Bin 0 -> 15144 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 | 987 ++ .../pip/_vendor/pygments/formatters/img.py | 685 ++ .../pip/_vendor/pygments/formatters/irc.py | 154 + .../pip/_vendor/pygments/formatters/latex.py | 518 + .../pip/_vendor/pygments/formatters/other.py | 160 + .../pygments/formatters/pangomarkup.py | 83 + .../pip/_vendor/pygments/formatters/rtf.py | 349 + .../pip/_vendor/pygments/formatters/svg.py | 185 + .../_vendor/pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + .../pip/_vendor/pygments/lexer.py | 963 ++ .../pip/_vendor/pygments/lexers/__init__.py | 362 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 14647 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 68289 bytes .../lexers/__pycache__/python.cpython-312.pyc | Bin 0 -> 42993 bytes .../pip/_vendor/pygments/lexers/_mapping.py | 589 ++ .../pip/_vendor/pygments/lexers/python.py | 1198 +++ .../pip/_vendor/pygments/modeline.py | 43 + .../pip/_vendor/pygments/plugin.py | 72 + .../pip/_vendor/pygments/regexopt.py | 91 + .../pip/_vendor/pygments/scanner.py | 104 + .../pip/_vendor/pygments/sphinxext.py | 247 + .../pip/_vendor/pygments/style.py | 203 + .../pip/_vendor/pygments/styles/__init__.py | 61 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2690 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 3674 bytes .../pip/_vendor/pygments/styles/_mapping.py | 54 + .../pip/_vendor/pygments/token.py | 214 + .../pip/_vendor/pygments/unistring.py | 153 + .../pip/_vendor/pygments/util.py | 324 + .../pip/_vendor/pyproject_hooks/__init__.py | 23 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 639 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 400 bytes .../__pycache__/_impl.cpython-312.pyc | Bin 0 -> 14719 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 -> 1106 bytes .../__pycache__/_in_process.cpython-312.pyc | Bin 0 -> 14379 bytes .../_in_process/_in_process.py | 353 + .../pip/_vendor/requests/__init__.py | 179 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5279 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 610 bytes .../_internal_utils.cpython-312.pyc | Bin 0 -> 2050 bytes .../__pycache__/adapters.cpython-312.pyc | Bin 0 -> 28457 bytes .../requests/__pycache__/api.cpython-312.pyc | Bin 0 -> 7217 bytes .../requests/__pycache__/auth.cpython-312.pyc | Bin 0 -> 13947 bytes .../__pycache__/certs.cpython-312.pyc | Bin 0 -> 948 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 1703 bytes .../__pycache__/cookies.cpython-312.pyc | Bin 0 -> 25224 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7624 bytes .../requests/__pycache__/help.cpython-312.pyc | Bin 0 -> 4254 bytes .../__pycache__/hooks.cpython-312.pyc | Bin 0 -> 1077 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 35454 bytes .../__pycache__/packages.cpython-312.pyc | Bin 0 -> 1292 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 27872 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 6049 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 5649 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 36392 bytes .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 50 + .../pip/_vendor/requests/adapters.py | 719 ++ .../site-packages/pip/_vendor/requests/api.py | 157 + .../pip/_vendor/requests/auth.py | 314 + .../pip/_vendor/requests/certs.py | 24 + .../pip/_vendor/requests/compat.py | 78 + .../pip/_vendor/requests/cookies.py | 561 ++ .../pip/_vendor/requests/exceptions.py | 151 + .../pip/_vendor/requests/help.py | 127 + .../pip/_vendor/requests/hooks.py | 33 + .../pip/_vendor/requests/models.py | 1037 ++ .../pip/_vendor/requests/packages.py | 25 + .../pip/_vendor/requests/sessions.py | 831 ++ .../pip/_vendor/requests/status_codes.py | 128 + .../pip/_vendor/requests/structures.py | 99 + .../pip/_vendor/requests/utils.py | 1096 +++ .../pip/_vendor/resolvelib/__init__.py | 26 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 660 bytes .../__pycache__/providers.cpython-312.pyc | Bin 0 -> 6877 bytes .../__pycache__/reporters.cpython-312.pyc | Bin 0 -> 2680 bytes .../__pycache__/resolvers.cpython-312.pyc | Bin 0 -> 25906 bytes .../__pycache__/structs.cpython-312.pyc | Bin 0 -> 10526 bytes .../pip/_vendor/resolvelib/compat/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 226 bytes .../collections_abc.cpython-312.pyc | Bin 0 -> 446 bytes .../resolvelib/compat/collections_abc.py | 6 + .../pip/_vendor/resolvelib/providers.py | 133 + .../pip/_vendor/resolvelib/py.typed | 0 .../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 | 273 + .../rich/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7041 bytes .../rich/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 10318 bytes .../__pycache__/_cell_widths.cpython-312.pyc | Bin 0 -> 7898 bytes .../__pycache__/_emoji_codes.cpython-312.pyc | Bin 0 -> 206002 bytes .../_emoji_replace.cpython-312.pyc | Bin 0 -> 1755 bytes .../_export_format.cpython-312.pyc | Bin 0 -> 2375 bytes .../__pycache__/_extension.cpython-312.pyc | Bin 0 -> 563 bytes .../rich/__pycache__/_fileno.cpython-312.pyc | Bin 0 -> 881 bytes .../rich/__pycache__/_inspect.cpython-312.pyc | Bin 0 -> 12099 bytes .../__pycache__/_log_render.cpython-312.pyc | Bin 0 -> 4173 bytes .../rich/__pycache__/_loop.cpython-312.pyc | Bin 0 -> 1896 bytes .../__pycache__/_null_file.cpython-312.pyc | Bin 0 -> 3646 bytes .../__pycache__/_palettes.cpython-312.pyc | Bin 0 -> 5186 bytes .../rich/__pycache__/_pick.cpython-312.pyc | Bin 0 -> 747 bytes .../rich/__pycache__/_ratio.cpython-312.pyc | Bin 0 -> 6596 bytes .../__pycache__/_spinners.cpython-312.pyc | Bin 0 -> 13205 bytes .../rich/__pycache__/_stack.cpython-312.pyc | Bin 0 -> 991 bytes .../rich/__pycache__/_timer.cpython-312.pyc | Bin 0 -> 891 bytes .../_win32_console.cpython-312.pyc | Bin 0 -> 29002 bytes .../rich/__pycache__/_windows.cpython-312.pyc | Bin 0 -> 2516 bytes .../_windows_renderer.cpython-312.pyc | Bin 0 -> 3589 bytes .../rich/__pycache__/_wrap.cpython-312.pyc | Bin 0 -> 3352 bytes .../rich/__pycache__/abc.cpython-312.pyc | Bin 0 -> 1634 bytes .../rich/__pycache__/align.cpython-312.pyc | Bin 0 -> 12313 bytes .../rich/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 9092 bytes .../rich/__pycache__/bar.cpython-312.pyc | Bin 0 -> 4298 bytes .../rich/__pycache__/box.cpython-312.pyc | Bin 0 -> 11864 bytes .../rich/__pycache__/cells.cpython-312.pyc | Bin 0 -> 5836 bytes .../rich/__pycache__/color.cpython-312.pyc | Bin 0 -> 26595 bytes .../__pycache__/color_triplet.cpython-312.pyc | Bin 0 -> 1727 bytes .../rich/__pycache__/columns.cpython-312.pyc | Bin 0 -> 8610 bytes .../rich/__pycache__/console.cpython-312.pyc | Bin 0 -> 113462 bytes .../__pycache__/constrain.cpython-312.pyc | Bin 0 -> 2284 bytes .../__pycache__/containers.cpython-312.pyc | Bin 0 -> 9236 bytes .../rich/__pycache__/control.cpython-312.pyc | Bin 0 -> 10967 bytes .../default_styles.cpython-312.pyc | Bin 0 -> 10392 bytes .../rich/__pycache__/diagnose.cpython-312.pyc | Bin 0 -> 1514 bytes .../rich/__pycache__/emoji.cpython-312.pyc | Bin 0 -> 4238 bytes .../rich/__pycache__/errors.cpython-312.pyc | Bin 0 -> 1871 bytes .../__pycache__/file_proxy.cpython-312.pyc | Bin 0 -> 3597 bytes .../rich/__pycache__/filesize.cpython-312.pyc | Bin 0 -> 3098 bytes .../__pycache__/highlighter.cpython-312.pyc | Bin 0 -> 9913 bytes .../rich/__pycache__/json.cpython-312.pyc | Bin 0 -> 6061 bytes .../rich/__pycache__/jupyter.cpython-312.pyc | Bin 0 -> 5234 bytes .../rich/__pycache__/layout.cpython-312.pyc | Bin 0 -> 20184 bytes .../rich/__pycache__/live.cpython-312.pyc | Bin 0 -> 19035 bytes .../__pycache__/live_render.cpython-312.pyc | Bin 0 -> 4915 bytes .../rich/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13580 bytes .../rich/__pycache__/markup.cpython-312.pyc | Bin 0 -> 9593 bytes .../rich/__pycache__/measure.cpython-312.pyc | Bin 0 -> 6404 bytes .../rich/__pycache__/padding.cpython-312.pyc | Bin 0 -> 7150 bytes .../rich/__pycache__/pager.cpython-312.pyc | Bin 0 -> 1837 bytes .../rich/__pycache__/palette.cpython-312.pyc | Bin 0 -> 5323 bytes .../rich/__pycache__/panel.cpython-312.pyc | Bin 0 -> 12209 bytes .../rich/__pycache__/pretty.cpython-312.pyc | Bin 0 -> 40173 bytes .../rich/__pycache__/progress.cpython-312.pyc | Bin 0 -> 74961 bytes .../__pycache__/progress_bar.cpython-312.pyc | Bin 0 -> 10403 bytes .../rich/__pycache__/prompt.cpython-312.pyc | Bin 0 -> 14813 bytes .../rich/__pycache__/protocol.cpython-312.pyc | Bin 0 -> 1818 bytes .../rich/__pycache__/region.cpython-312.pyc | Bin 0 -> 593 bytes .../rich/__pycache__/repr.cpython-312.pyc | Bin 0 -> 6639 bytes .../rich/__pycache__/rule.cpython-312.pyc | Bin 0 -> 6594 bytes .../rich/__pycache__/scope.cpython-312.pyc | Bin 0 -> 3851 bytes .../rich/__pycache__/screen.cpython-312.pyc | Bin 0 -> 2505 bytes .../rich/__pycache__/segment.cpython-312.pyc | Bin 0 -> 28141 bytes .../rich/__pycache__/spinner.cpython-312.pyc | Bin 0 -> 6090 bytes .../rich/__pycache__/status.cpython-312.pyc | Bin 0 -> 6087 bytes .../rich/__pycache__/style.cpython-312.pyc | Bin 0 -> 33526 bytes .../rich/__pycache__/styled.cpython-312.pyc | Bin 0 -> 2165 bytes .../rich/__pycache__/syntax.cpython-312.pyc | Bin 0 -> 39974 bytes .../rich/__pycache__/table.cpython-312.pyc | Bin 0 -> 43565 bytes .../terminal_theme.cpython-312.pyc | Bin 0 -> 3374 bytes .../rich/__pycache__/text.cpython-312.pyc | Bin 0 -> 60875 bytes .../rich/__pycache__/theme.cpython-312.pyc | Bin 0 -> 6361 bytes .../rich/__pycache__/themes.cpython-312.pyc | Bin 0 -> 340 bytes .../__pycache__/traceback.cpython-312.pyc | Bin 0 -> 31537 bytes .../rich/__pycache__/tree.cpython-312.pyc | Bin 0 -> 11462 bytes .../pip/_vendor/rich/_cell_widths.py | 454 + .../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 | 159 + .../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 | 71 + .../pip/_vendor/rich/_windows_renderer.py | 56 + .../site-packages/pip/_vendor/rich/_wrap.py | 93 + .../Lib/site-packages/pip/_vendor/rich/abc.py | 33 + .../site-packages/pip/_vendor/rich/align.py | 311 + .../site-packages/pip/_vendor/rich/ansi.py | 240 + .../Lib/site-packages/pip/_vendor/rich/bar.py | 93 + .../Lib/site-packages/pip/_vendor/rich/box.py | 480 + .../site-packages/pip/_vendor/rich/cells.py | 167 + .../site-packages/pip/_vendor/rich/color.py | 621 ++ .../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 | 139 + .../site-packages/pip/_vendor/rich/jupyter.py | 101 + .../site-packages/pip/_vendor/rich/layout.py | 442 + .../site-packages/pip/_vendor/rich/live.py | 375 + .../pip/_vendor/rich/live_render.py | 112 + .../site-packages/pip/_vendor/rich/logging.py | 289 + .../site-packages/pip/_vendor/rich/markup.py | 251 + .../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 | 312 + .../site-packages/pip/_vendor/rich/pretty.py | 995 ++ .../pip/_vendor/rich/progress.py | 1699 ++++ .../pip/_vendor/rich/progress_bar.py | 223 + .../site-packages/pip/_vendor/rich/prompt.py | 375 + .../pip/_vendor/rich/protocol.py | 42 + .../site-packages/pip/_vendor/rich/py.typed | 0 .../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 | 738 ++ .../site-packages/pip/_vendor/rich/spinner.py | 137 + .../site-packages/pip/_vendor/rich/status.py | 131 + .../site-packages/pip/_vendor/rich/style.py | 796 ++ .../site-packages/pip/_vendor/rich/styled.py | 42 + .../site-packages/pip/_vendor/rich/syntax.py | 958 ++ .../site-packages/pip/_vendor/rich/table.py | 1000 ++ .../pip/_vendor/rich/terminal_theme.py | 153 + .../site-packages/pip/_vendor/rich/text.py | 1357 +++ .../site-packages/pip/_vendor/rich/theme.py | 115 + .../site-packages/pip/_vendor/rich/themes.py | 5 + .../pip/_vendor/rich/traceback.py | 753 ++ .../site-packages/pip/_vendor/rich/tree.py | 249 + .../pip/_vendor/tomli/__init__.py | 11 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 410 bytes .../tomli/__pycache__/_parser.cpython-312.pyc | Bin 0 -> 26925 bytes .../tomli/__pycache__/_re.cpython-312.pyc | Bin 0 -> 3934 bytes .../tomli/__pycache__/_types.cpython-312.pyc | Bin 0 -> 392 bytes .../pip/_vendor/tomli/_parser.py | 691 ++ .../site-packages/pip/_vendor/tomli/_re.py | 107 + .../site-packages/pip/_vendor/tomli/_types.py | 10 + .../site-packages/pip/_vendor/tomli/py.typed | 1 + .../pip/_vendor/truststore/__init__.py | 36 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1370 bytes .../__pycache__/_api.cpython-312.pyc | Bin 0 -> 16799 bytes .../__pycache__/_macos.cpython-312.pyc | Bin 0 -> 19013 bytes .../__pycache__/_openssl.cpython-312.pyc | Bin 0 -> 2231 bytes .../_ssl_constants.cpython-312.pyc | Bin 0 -> 1125 bytes .../__pycache__/_windows.cpython-312.pyc | Bin 0 -> 15791 bytes .../pip/_vendor/truststore/_api.py | 316 + .../pip/_vendor/truststore/_macos.py | 571 ++ .../pip/_vendor/truststore/_openssl.py | 66 + .../pip/_vendor/truststore/_ssl_constants.py | 31 + .../pip/_vendor/truststore/_windows.py | 567 ++ .../pip/_vendor/truststore/py.typed | 0 .../pip/_vendor/typing_extensions.py | 3641 +++++++ .../pip/_vendor/urllib3/__init__.py | 102 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3431 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 16390 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 244 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 20429 bytes .../connectionpool.cpython-312.pyc | Bin 0 -> 36564 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 13519 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 10428 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 0 -> 4038 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 0 -> 20455 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 7320 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 33969 bytes .../pip/_vendor/urllib3/_collections.py | 355 + .../pip/_vendor/urllib3/_version.py | 2 + .../pip/_vendor/urllib3/connection.py | 572 ++ .../pip/_vendor/urllib3/connectionpool.py | 1140 +++ .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 224 bytes .../_appengine_environ.cpython-312.pyc | Bin 0 -> 1874 bytes .../__pycache__/appengine.cpython-312.pyc | Bin 0 -> 11590 bytes .../__pycache__/ntlmpool.cpython-312.pyc | Bin 0 -> 5740 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 0 -> 24474 bytes .../securetransport.cpython-312.pyc | Bin 0 -> 35527 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 0 -> 7537 bytes .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 241 bytes .../__pycache__/bindings.cpython-312.pyc | Bin 0 -> 17453 bytes .../__pycache__/low_level.cpython-312.pyc | Bin 0 -> 14789 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 | 920 ++ .../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 -> 225 bytes .../packages/__pycache__/six.cpython-312.pyc | Bin 0 -> 41281 bytes .../urllib3/packages/backports/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 235 bytes .../__pycache__/makefile.cpython-312.pyc | Bin 0 -> 1851 bytes .../weakref_finalize.cpython-312.pyc | Bin 0 -> 7362 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 | 540 + .../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 -> 1172 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 4773 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 1578 bytes .../util/__pycache__/queue.cpython-312.pyc | Bin 0 -> 1378 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 0 -> 4209 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 0 -> 3018 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 0 -> 21748 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 0 -> 15390 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 0 -> 5077 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 0 -> 10779 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 0 -> 11165 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 0 -> 15811 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 0 -> 4429 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 | 504 + .../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 + .../Lib/site-packages/pip/_vendor/vendor.txt | 18 + .venv/Lib/site-packages/pip/py.typed | 4 + .../pluggy-1.6.0.dist-info/INSTALLER | 1 + .../pluggy-1.6.0.dist-info/METADATA | 152 + .../pluggy-1.6.0.dist-info/RECORD | 23 + .../pluggy-1.6.0.dist-info/WHEEL | 5 + .../pluggy-1.6.0.dist-info/licenses/LICENSE | 21 + .../pluggy-1.6.0.dist-info/top_level.txt | 1 + .venv/Lib/site-packages/pluggy/__init__.py | 30 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 879 bytes .../__pycache__/_callers.cpython-312.pyc | Bin 0 -> 7034 bytes .../pluggy/__pycache__/_hooks.cpython-312.pyc | Bin 0 -> 27436 bytes .../__pycache__/_manager.cpython-312.pyc | Bin 0 -> 25598 bytes .../__pycache__/_result.cpython-312.pyc | Bin 0 -> 4166 bytes .../__pycache__/_tracing.cpython-312.pyc | Bin 0 -> 4038 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 664 bytes .../__pycache__/_warnings.cpython-312.pyc | Bin 0 -> 1317 bytes .venv/Lib/site-packages/pluggy/_callers.py | 169 + .venv/Lib/site-packages/pluggy/_hooks.py | 714 ++ .venv/Lib/site-packages/pluggy/_manager.py | 523 + .venv/Lib/site-packages/pluggy/_result.py | 107 + .venv/Lib/site-packages/pluggy/_tracing.py | 72 + .venv/Lib/site-packages/pluggy/_version.py | 21 + .venv/Lib/site-packages/pluggy/_warnings.py | 27 + .venv/Lib/site-packages/pluggy/py.typed | 0 .venv/Lib/site-packages/py.py | 15 + .../pydantic-2.11.5.dist-info/INSTALLER | 1 + .../pydantic-2.11.5.dist-info/METADATA | 746 ++ .../pydantic-2.11.5.dist-info/RECORD | 215 + .../pydantic-2.11.5.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .venv/Lib/site-packages/pydantic/__init__.py | 445 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 14205 bytes .../__pycache__/_migration.cpython-312.pyc | Bin 0 -> 10892 bytes .../alias_generators.cpython-312.pyc | Bin 0 -> 3302 bytes .../__pycache__/aliases.cpython-312.pyc | Bin 0 -> 6582 bytes .../annotated_handlers.cpython-312.pyc | Bin 0 -> 5516 bytes .../class_validators.cpython-312.pyc | Bin 0 -> 390 bytes .../__pycache__/color.cpython-312.pyc | Bin 0 -> 30196 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 7077 bytes .../__pycache__/dataclasses.cpython-312.pyc | Bin 0 -> 14880 bytes .../datetime_parse.cpython-312.pyc | Bin 0 -> 390 bytes .../__pycache__/decorator.cpython-312.pyc | Bin 0 -> 380 bytes .../__pycache__/env_settings.cpython-312.pyc | Bin 0 -> 386 bytes .../error_wrappers.cpython-312.pyc | Bin 0 -> 390 bytes .../__pycache__/errors.cpython-312.pyc | Bin 0 -> 7651 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 63916 bytes .../functional_serializers.cpython-312.pyc | Bin 0 -> 17976 bytes .../functional_validators.cpython-312.pyc | Bin 0 -> 32199 bytes .../__pycache__/generics.cpython-312.pyc | Bin 0 -> 378 bytes .../pydantic/__pycache__/json.cpython-312.pyc | Bin 0 -> 370 bytes .../__pycache__/json_schema.cpython-312.pyc | Bin 0 -> 113748 bytes .../pydantic/__pycache__/main.cpython-312.pyc | Bin 0 -> 74327 bytes .../pydantic/__pycache__/mypy.cpython-312.pyc | Bin 0 -> 61988 bytes .../__pycache__/networks.cpython-312.pyc | Bin 0 -> 49582 bytes .../__pycache__/parse.cpython-312.pyc | Bin 0 -> 372 bytes .../__pycache__/root_model.cpython-312.pyc | Bin 0 -> 7861 bytes .../__pycache__/schema.cpython-312.pyc | Bin 0 -> 374 bytes .../__pycache__/tools.cpython-312.pyc | Bin 0 -> 372 bytes .../__pycache__/type_adapter.cpython-312.pyc | Bin 0 -> 31912 bytes .../__pycache__/types.cpython-312.pyc | Bin 0 -> 96482 bytes .../__pycache__/typing.cpython-312.pyc | Bin 0 -> 370 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 372 bytes .../validate_call_decorator.cpython-312.pyc | Bin 0 -> 5468 bytes .../__pycache__/validators.cpython-312.pyc | Bin 0 -> 382 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 3933 bytes .../__pycache__/warnings.cpython-312.pyc | Bin 0 -> 5762 bytes .../pydantic/_internal/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 215 bytes .../__pycache__/_config.cpython-312.pyc | Bin 0 -> 15165 bytes .../_core_metadata.cpython-312.pyc | Bin 0 -> 4635 bytes .../__pycache__/_core_utils.cpython-312.pyc | Bin 0 -> 8071 bytes .../__pycache__/_dataclasses.cpython-312.pyc | Bin 0 -> 9582 bytes .../__pycache__/_decorators.cpython-312.pyc | Bin 0 -> 35569 bytes .../_decorators_v1.cpython-312.pyc | Bin 0 -> 8635 bytes .../_discriminated_union.cpython-312.pyc | Bin 0 -> 20866 bytes .../_docs_extraction.cpython-312.pyc | Bin 0 -> 5230 bytes .../__pycache__/_fields.cpython-312.pyc | Bin 0 -> 18210 bytes .../__pycache__/_forward_ref.cpython-312.pyc | Bin 0 -> 1324 bytes .../_generate_schema.cpython-312.pyc | Bin 0 -> 132608 bytes .../__pycache__/_generics.cpython-312.pyc | Bin 0 -> 24325 bytes .../__pycache__/_git.cpython-312.pyc | Bin 0 -> 1530 bytes .../__pycache__/_import_utils.cpython-312.pyc | Bin 0 -> 843 bytes .../_internal_dataclass.cpython-312.pyc | Bin 0 -> 364 bytes .../_known_annotated_metadata.cpython-312.pyc | Bin 0 -> 13971 bytes .../__pycache__/_mock_val_ser.cpython-312.pyc | Bin 0 -> 11113 bytes .../_model_construction.cpython-312.pyc | Bin 0 -> 33948 bytes .../_namespace_utils.cpython-312.pyc | Bin 0 -> 12333 bytes .../__pycache__/_repr.cpython-312.pyc | Bin 0 -> 7715 bytes .../_schema_gather.cpython-312.pyc | Bin 0 -> 7660 bytes .../_schema_generation_shared.cpython-312.pyc | Bin 0 -> 6241 bytes .../__pycache__/_serializers.cpython-312.pyc | Bin 0 -> 2131 bytes .../__pycache__/_signature.cpython-312.pyc | Bin 0 -> 6781 bytes .../__pycache__/_typing_extra.cpython-312.pyc | Bin 0 -> 27342 bytes .../__pycache__/_utils.cpython-312.pyc | Bin 0 -> 19616 bytes .../_validate_call.cpython-312.pyc | Bin 0 -> 7002 bytes .../__pycache__/_validators.cpython-312.pyc | Bin 0 -> 23040 bytes .../pydantic/_internal/_config.py | 373 + .../pydantic/_internal/_core_metadata.py | 97 + .../pydantic/_internal/_core_utils.py | 182 + .../pydantic/_internal/_dataclasses.py | 235 + .../pydantic/_internal/_decorators.py | 838 ++ .../pydantic/_internal/_decorators_v1.py | 174 + .../_internal/_discriminated_union.py | 479 + .../pydantic/_internal/_docs_extraction.py | 108 + .../pydantic/_internal/_fields.py | 463 + .../pydantic/_internal/_forward_ref.py | 23 + .../pydantic/_internal/_generate_schema.py | 2886 ++++++ .../pydantic/_internal/_generics.py | 547 ++ .../site-packages/pydantic/_internal/_git.py | 27 + .../pydantic/_internal/_import_utils.py | 20 + .../pydantic/_internal/_internal_dataclass.py | 7 + .../_internal/_known_annotated_metadata.py | 393 + .../pydantic/_internal/_mock_val_ser.py | 228 + .../pydantic/_internal/_model_construction.py | 792 ++ .../pydantic/_internal/_namespace_utils.py | 293 + .../site-packages/pydantic/_internal/_repr.py | 125 + .../pydantic/_internal/_schema_gather.py | 209 + .../_internal/_schema_generation_shared.py | 125 + .../pydantic/_internal/_serializers.py | 53 + .../pydantic/_internal/_signature.py | 188 + .../pydantic/_internal/_typing_extra.py | 714 ++ .../pydantic/_internal/_utils.py | 431 + .../pydantic/_internal/_validate_call.py | 140 + .../pydantic/_internal/_validators.py | 532 + .../Lib/site-packages/pydantic/_migration.py | 308 + .../pydantic/alias_generators.py | 62 + .venv/Lib/site-packages/pydantic/aliases.py | 135 + .../pydantic/annotated_handlers.py | 122 + .../pydantic/class_validators.py | 5 + .venv/Lib/site-packages/pydantic/color.py | 604 ++ .venv/Lib/site-packages/pydantic/config.py | 1213 +++ .../Lib/site-packages/pydantic/dataclasses.py | 374 + .../site-packages/pydantic/datetime_parse.py | 5 + .venv/Lib/site-packages/pydantic/decorator.py | 5 + .../pydantic/deprecated/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 216 bytes .../class_validators.cpython-312.pyc | Bin 0 -> 11762 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 4093 bytes .../copy_internals.cpython-312.pyc | Bin 0 -> 8692 bytes .../__pycache__/decorator.cpython-312.pyc | Bin 0 -> 14044 bytes .../__pycache__/json.cpython-312.pyc | Bin 0 -> 6192 bytes .../__pycache__/parse.cpython-312.pyc | Bin 0 -> 3422 bytes .../__pycache__/tools.cpython-312.pyc | Bin 0 -> 3558 bytes .../pydantic/deprecated/class_validators.py | 256 + .../pydantic/deprecated/config.py | 72 + .../pydantic/deprecated/copy_internals.py | 224 + .../pydantic/deprecated/decorator.py | 284 + .../site-packages/pydantic/deprecated/json.py | 141 + .../pydantic/deprecated/parse.py | 80 + .../pydantic/deprecated/tools.py | 103 + .../site-packages/pydantic/env_settings.py | 5 + .../site-packages/pydantic/error_wrappers.py | 5 + .venv/Lib/site-packages/pydantic/errors.py | 189 + .../pydantic/experimental/__init__.py | 10 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 586 bytes .../arguments_schema.cpython-312.pyc | Bin 0 -> 2422 bytes .../__pycache__/pipeline.cpython-312.pyc | Bin 0 -> 34783 bytes .../pydantic/experimental/arguments_schema.py | 44 + .../pydantic/experimental/pipeline.py | 667 ++ .venv/Lib/site-packages/pydantic/fields.py | 1555 +++ .../pydantic/functional_serializers.py | 450 + .../pydantic/functional_validators.py | 828 ++ .venv/Lib/site-packages/pydantic/generics.py | 5 + .venv/Lib/site-packages/pydantic/json.py | 5 + .../Lib/site-packages/pydantic/json_schema.py | 2695 +++++ .venv/Lib/site-packages/pydantic/main.py | 1773 ++++ .venv/Lib/site-packages/pydantic/mypy.py | 1380 +++ .venv/Lib/site-packages/pydantic/networks.py | 1312 +++ .venv/Lib/site-packages/pydantic/parse.py | 5 + .../site-packages/pydantic/plugin/__init__.py | 188 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 8533 bytes .../__pycache__/_loader.cpython-312.pyc | Bin 0 -> 2422 bytes .../_schema_validator.cpython-312.pyc | Bin 0 -> 6936 bytes .../site-packages/pydantic/plugin/_loader.py | 57 + .../pydantic/plugin/_schema_validator.py | 140 + .venv/Lib/site-packages/pydantic/py.typed | 0 .../Lib/site-packages/pydantic/root_model.py | 157 + .venv/Lib/site-packages/pydantic/schema.py | 5 + .venv/Lib/site-packages/pydantic/tools.py | 5 + .../site-packages/pydantic/type_adapter.py | 727 ++ .venv/Lib/site-packages/pydantic/types.py | 3285 +++++++ .venv/Lib/site-packages/pydantic/typing.py | 5 + .venv/Lib/site-packages/pydantic/utils.py | 5 + .../Lib/site-packages/pydantic/v1/__init__.py | 131 + .../v1/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2239 bytes .../_hypothesis_plugin.cpython-312.pyc | Bin 0 -> 20524 bytes .../annotated_types.cpython-312.pyc | Bin 0 -> 3903 bytes .../class_validators.cpython-312.pyc | Bin 0 -> 19663 bytes .../v1/__pycache__/color.cpython-312.pyc | Bin 0 -> 25869 bytes .../v1/__pycache__/config.cpython-312.pyc | Bin 0 -> 8418 bytes .../__pycache__/dataclasses.cpython-312.pyc | Bin 0 -> 22763 bytes .../datetime_parse.cpython-312.pyc | Bin 0 -> 10371 bytes .../v1/__pycache__/decorator.cpython-312.pyc | Bin 0 -> 13936 bytes .../__pycache__/env_settings.cpython-312.pyc | Bin 0 -> 17738 bytes .../error_wrappers.cpython-312.pyc | Bin 0 -> 8946 bytes .../v1/__pycache__/errors.cpython-312.pyc | Bin 0 -> 29621 bytes .../v1/__pycache__/fields.cpython-312.pyc | Bin 0 -> 57461 bytes .../v1/__pycache__/generics.cpython-312.pyc | Bin 0 -> 17011 bytes .../v1/__pycache__/json.cpython-312.pyc | Bin 0 -> 5229 bytes .../v1/__pycache__/main.cpython-312.pyc | Bin 0 -> 48235 bytes .../v1/__pycache__/mypy.cpython-312.pyc | Bin 0 -> 46437 bytes .../v1/__pycache__/networks.cpython-312.pyc | Bin 0 -> 29568 bytes .../v1/__pycache__/parse.cpython-312.pyc | Bin 0 -> 2766 bytes .../v1/__pycache__/schema.cpython-312.pyc | Bin 0 -> 48494 bytes .../v1/__pycache__/tools.cpython-312.pyc | Bin 0 -> 3899 bytes .../v1/__pycache__/types.cpython-312.pyc | Bin 0 -> 48506 bytes .../v1/__pycache__/typing.cpython-312.pyc | Bin 0 -> 22598 bytes .../v1/__pycache__/utils.cpython-312.pyc | Bin 0 -> 35256 bytes .../v1/__pycache__/validators.cpython-312.pyc | Bin 0 -> 30914 bytes .../v1/__pycache__/version.cpython-312.pyc | Bin 0 -> 1977 bytes .../pydantic/v1/_hypothesis_plugin.py | 391 + .../pydantic/v1/annotated_types.py | 72 + .../pydantic/v1/class_validators.py | 361 + .venv/Lib/site-packages/pydantic/v1/color.py | 494 + .venv/Lib/site-packages/pydantic/v1/config.py | 191 + .../site-packages/pydantic/v1/dataclasses.py | 500 + .../pydantic/v1/datetime_parse.py | 248 + .../site-packages/pydantic/v1/decorator.py | 264 + .../site-packages/pydantic/v1/env_settings.py | 350 + .../pydantic/v1/error_wrappers.py | 161 + .venv/Lib/site-packages/pydantic/v1/errors.py | 646 ++ .venv/Lib/site-packages/pydantic/v1/fields.py | 1253 +++ .../Lib/site-packages/pydantic/v1/generics.py | 400 + .venv/Lib/site-packages/pydantic/v1/json.py | 112 + .venv/Lib/site-packages/pydantic/v1/main.py | 1113 +++ .venv/Lib/site-packages/pydantic/v1/mypy.py | 949 ++ .../Lib/site-packages/pydantic/v1/networks.py | 747 ++ .venv/Lib/site-packages/pydantic/v1/parse.py | 66 + .venv/Lib/site-packages/pydantic/v1/py.typed | 0 .venv/Lib/site-packages/pydantic/v1/schema.py | 1163 +++ .venv/Lib/site-packages/pydantic/v1/tools.py | 92 + .venv/Lib/site-packages/pydantic/v1/types.py | 1205 +++ .venv/Lib/site-packages/pydantic/v1/typing.py | 615 ++ .venv/Lib/site-packages/pydantic/v1/utils.py | 806 ++ .../site-packages/pydantic/v1/validators.py | 768 ++ .../Lib/site-packages/pydantic/v1/version.py | 38 + .../pydantic/validate_call_decorator.py | 116 + .../Lib/site-packages/pydantic/validators.py | 5 + .venv/Lib/site-packages/pydantic/version.py | 84 + .venv/Lib/site-packages/pydantic/warnings.py | 96 + .../pydantic_core-2.33.2.dist-info/INSTALLER | 1 + .../pydantic_core-2.33.2.dist-info/METADATA | 160 + .../pydantic_core-2.33.2.dist-info/RECORD | 12 + .../pydantic_core-2.33.2.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .../site-packages/pydantic_core/__init__.py | 144 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3121 bytes .../__pycache__/core_schema.cpython-312.pyc | Bin 0 -> 150607 bytes .../_pydantic_core.cp312-win_amd64.pyd | Bin 0 -> 5310464 bytes .../pydantic_core/_pydantic_core.pyi | 1039 ++ .../pydantic_core/core_schema.py | 4325 ++++++++ .../Lib/site-packages/pydantic_core/py.typed | 0 .../pytest-8.3.5.dist-info/AUTHORS | 474 + .../pytest-8.3.5.dist-info/INSTALLER | 1 + .../pytest-8.3.5.dist-info/LICENSE | 21 + .../pytest-8.3.5.dist-info/METADATA | 213 + .../pytest-8.3.5.dist-info/RECORD | 155 + .../pytest-8.3.5.dist-info/REQUESTED | 0 .../pytest-8.3.5.dist-info/WHEEL | 5 + .../pytest-8.3.5.dist-info/entry_points.txt | 3 + .../pytest-8.3.5.dist-info/top_level.txt | 3 + .venv/Lib/site-packages/pytest/__init__.py | 172 + .venv/Lib/site-packages/pytest/__main__.py | 9 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4202 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 452 bytes .venv/Lib/site-packages/pytest/py.typed | 0 .../requests-2.32.3.dist-info/INSTALLER | 1 + .../requests-2.32.3.dist-info/LICENSE | 175 + .../requests-2.32.3.dist-info/METADATA | 119 + .../requests-2.32.3.dist-info/RECORD | 43 + .../requests-2.32.3.dist-info/REQUESTED | 0 .../requests-2.32.3.dist-info/WHEEL | 5 + .../requests-2.32.3.dist-info/top_level.txt | 1 + .venv/Lib/site-packages/requests/__init__.py | 184 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5434 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 598 bytes .../_internal_utils.cpython-312.pyc | Bin 0 -> 2038 bytes .../__pycache__/adapters.cpython-312.pyc | Bin 0 -> 28372 bytes .../requests/__pycache__/api.cpython-312.pyc | Bin 0 -> 7205 bytes .../requests/__pycache__/auth.cpython-312.pyc | Bin 0 -> 13935 bytes .../__pycache__/certs.cpython-312.pyc | Bin 0 -> 680 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 2094 bytes .../__pycache__/cookies.cpython-312.pyc | Bin 0 -> 25212 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7599 bytes .../requests/__pycache__/help.cpython-312.pyc | Bin 0 -> 4341 bytes .../__pycache__/hooks.cpython-312.pyc | Bin 0 -> 1065 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 35370 bytes .../__pycache__/packages.cpython-312.pyc | Bin 0 -> 1132 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 27860 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 6037 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 5637 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 36368 bytes .../Lib/site-packages/requests/__version__.py | 14 + .../site-packages/requests/_internal_utils.py | 50 + .venv/Lib/site-packages/requests/adapters.py | 719 ++ .venv/Lib/site-packages/requests/api.py | 157 + .venv/Lib/site-packages/requests/auth.py | 314 + .venv/Lib/site-packages/requests/certs.py | 17 + .venv/Lib/site-packages/requests/compat.py | 94 + .venv/Lib/site-packages/requests/cookies.py | 561 ++ .../Lib/site-packages/requests/exceptions.py | 151 + .venv/Lib/site-packages/requests/help.py | 134 + .venv/Lib/site-packages/requests/hooks.py | 33 + .venv/Lib/site-packages/requests/models.py | 1037 ++ .venv/Lib/site-packages/requests/packages.py | 23 + .venv/Lib/site-packages/requests/sessions.py | 831 ++ .../site-packages/requests/status_codes.py | 128 + .../Lib/site-packages/requests/structures.py | 99 + .venv/Lib/site-packages/requests/utils.py | 1096 +++ .../sniffio-1.3.1.dist-info/INSTALLER | 1 + .../sniffio-1.3.1.dist-info/LICENSE | 3 + .../sniffio-1.3.1.dist-info/LICENSE.APACHE2 | 202 + .../sniffio-1.3.1.dist-info/LICENSE.MIT | 20 + .../sniffio-1.3.1.dist-info/METADATA | 104 + .../sniffio-1.3.1.dist-info/RECORD | 19 + .../sniffio-1.3.1.dist-info/WHEEL | 5 + .../sniffio-1.3.1.dist-info/top_level.txt | 1 + .venv/Lib/site-packages/sniffio/__init__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 480 bytes .../sniffio/__pycache__/_impl.cpython-312.pyc | Bin 0 -> 3191 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 232 bytes .venv/Lib/site-packages/sniffio/_impl.py | 95 + .../site-packages/sniffio/_tests/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 211 bytes .../__pycache__/test_sniffio.cpython-312.pyc | Bin 0 -> 3872 bytes .../sniffio/_tests/test_sniffio.py | 84 + .venv/Lib/site-packages/sniffio/_version.py | 3 + .venv/Lib/site-packages/sniffio/py.typed | 0 .../starlette-0.46.2.dist-info/INSTALLER | 1 + .../starlette-0.46.2.dist-info/METADATA | 176 + .../starlette-0.46.2.dist-info/RECORD | 74 + .../starlette-0.46.2.dist-info/WHEEL | 4 + .../licenses/LICENSE.md | 27 + .venv/Lib/site-packages/starlette/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 233 bytes .../_exception_handler.cpython-312.pyc | Bin 0 -> 3105 bytes .../__pycache__/_utils.cpython-312.pyc | Bin 0 -> 5371 bytes .../__pycache__/applications.cpython-312.pyc | Bin 0 -> 13038 bytes .../authentication.cpython-312.pyc | Bin 0 -> 7863 bytes .../__pycache__/background.cpython-312.pyc | Bin 0 -> 2626 bytes .../__pycache__/concurrency.cpython-312.pyc | Bin 0 -> 3260 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 7552 bytes .../__pycache__/convertors.cpython-312.pyc | Bin 0 -> 4803 bytes .../datastructures.cpython-312.pyc | Bin 0 -> 39336 bytes .../__pycache__/endpoints.cpython-312.pyc | Bin 0 -> 7863 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 2417 bytes .../__pycache__/formparsers.cpython-312.pyc | Bin 0 -> 14256 bytes .../__pycache__/requests.cpython-312.pyc | Bin 0 -> 16285 bytes .../__pycache__/responses.cpython-312.pyc | Bin 0 -> 29254 bytes .../__pycache__/routing.cpython-312.pyc | Bin 0 -> 43977 bytes .../__pycache__/schemas.cpython-312.pyc | Bin 0 -> 7062 bytes .../__pycache__/staticfiles.cpython-312.pyc | Bin 0 -> 11617 bytes .../__pycache__/status.cpython-312.pyc | Bin 0 -> 3660 bytes .../__pycache__/templating.cpython-312.pyc | Bin 0 -> 10054 bytes .../__pycache__/testclient.cpython-312.pyc | Bin 0 -> 33323 bytes .../__pycache__/types.cpython-312.pyc | Bin 0 -> 1807 bytes .../__pycache__/websockets.cpython-312.pyc | Bin 0 -> 11839 bytes .../starlette/_exception_handler.py | 65 + .venv/Lib/site-packages/starlette/_utils.py | 100 + .../site-packages/starlette/applications.py | 249 + .../site-packages/starlette/authentication.py | 147 + .../Lib/site-packages/starlette/background.py | 41 + .../site-packages/starlette/concurrency.py | 62 + .venv/Lib/site-packages/starlette/config.py | 138 + .../Lib/site-packages/starlette/convertors.py | 89 + .../site-packages/starlette/datastructures.py | 674 ++ .../Lib/site-packages/starlette/endpoints.py | 122 + .../Lib/site-packages/starlette/exceptions.py | 33 + .../site-packages/starlette/formparsers.py | 275 + .../starlette/middleware/__init__.py | 42 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2569 bytes .../authentication.cpython-312.pyc | Bin 0 -> 2894 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 11263 bytes .../__pycache__/cors.cpython-312.pyc | Bin 0 -> 7455 bytes .../__pycache__/errors.cpython-312.pyc | Bin 0 -> 9985 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 4107 bytes .../__pycache__/gzip.cpython-312.pyc | Bin 0 -> 8564 bytes .../__pycache__/httpsredirect.cpython-312.pyc | Bin 0 -> 1792 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 4672 bytes .../__pycache__/trustedhost.cpython-312.pyc | Bin 0 -> 3135 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 8522 bytes .../starlette/middleware/authentication.py | 52 + .../starlette/middleware/base.py | 222 + .../starlette/middleware/cors.py | 172 + .../starlette/middleware/errors.py | 260 + .../starlette/middleware/exceptions.py | 72 + .../starlette/middleware/gzip.py | 141 + .../starlette/middleware/httpsredirect.py | 19 + .../starlette/middleware/sessions.py | 85 + .../starlette/middleware/trustedhost.py | 60 + .../starlette/middleware/wsgi.py | 152 + .venv/Lib/site-packages/starlette/py.typed | 0 .venv/Lib/site-packages/starlette/requests.py | 322 + .../Lib/site-packages/starlette/responses.py | 536 + .venv/Lib/site-packages/starlette/routing.py | 874 ++ .venv/Lib/site-packages/starlette/schemas.py | 147 + .../site-packages/starlette/staticfiles.py | 220 + .venv/Lib/site-packages/starlette/status.py | 95 + .../Lib/site-packages/starlette/templating.py | 216 + .../Lib/site-packages/starlette/testclient.py | 731 ++ .venv/Lib/site-packages/starlette/types.py | 24 + .../Lib/site-packages/starlette/websockets.py | 195 + .../INSTALLER | 1 + .../METADATA | 68 + .../typing_extensions-4.13.2.dist-info/RECORD | 7 + .../typing_extensions-4.13.2.dist-info/WHEEL | 4 + .../licenses/LICENSE | 279 + .venv/Lib/site-packages/typing_extensions.py | 4584 +++++++++ .../INSTALLER | 1 + .../METADATA | 49 + .../typing_inspection-0.4.1.dist-info/RECORD | 13 + .../typing_inspection-0.4.1.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .../typing_inspection/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 214 bytes .../__pycache__/introspection.cpython-312.pyc | Bin 0 -> 17811 bytes .../typing_objects.cpython-312.pyc | Bin 0 -> 17179 bytes .../typing_inspection/introspection.py | 587 ++ .../site-packages/typing_inspection/py.typed | 0 .../typing_inspection/typing_objects.py | 596 ++ .../typing_inspection/typing_objects.pyi | 406 + .../urllib3-2.4.0.dist-info/INSTALLER | 1 + .../urllib3-2.4.0.dist-info/METADATA | 154 + .../urllib3-2.4.0.dist-info/RECORD | 79 + .../urllib3-2.4.0.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 21 + .venv/Lib/site-packages/urllib3/__init__.py | 211 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7329 bytes .../_base_connection.cpython-312.pyc | Bin 0 -> 6879 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 22480 bytes .../_request_methods.cpython-312.pyc | Bin 0 -> 10621 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 665 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 36171 bytes .../connectionpool.cpython-312.pyc | Bin 0 -> 39749 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 16630 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 12023 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 0 -> 3498 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 0 -> 24050 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 51071 bytes .../site-packages/urllib3/_base_connection.py | 165 + .../Lib/site-packages/urllib3/_collections.py | 479 + .../site-packages/urllib3/_request_methods.py | 278 + .venv/Lib/site-packages/urllib3/_version.py | 21 + .venv/Lib/site-packages/urllib3/connection.py | 1044 ++ .../site-packages/urllib3/connectionpool.py | 1178 +++ .../site-packages/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 212 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 0 -> 28236 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 0 -> 8190 bytes .../urllib3/contrib/emscripten/__init__.py | 16 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 920 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 10256 bytes .../__pycache__/fetch.cpython-312.pyc | Bin 0 -> 28133 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 1440 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 12228 bytes .../urllib3/contrib/emscripten/connection.py | 255 + .../emscripten/emscripten_fetch_worker.js | 110 + .../urllib3/contrib/emscripten/fetch.py | 708 ++ .../urllib3/contrib/emscripten/request.py | 22 + .../urllib3/contrib/emscripten/response.py | 277 + .../urllib3/contrib/pyopenssl.py | 564 ++ .../site-packages/urllib3/contrib/socks.py | 228 + .venv/Lib/site-packages/urllib3/exceptions.py | 335 + .venv/Lib/site-packages/urllib3/fields.py | 341 + .venv/Lib/site-packages/urllib3/filepost.py | 89 + .../site-packages/urllib3/http2/__init__.py | 53 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1765 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 16986 bytes .../http2/__pycache__/probe.cpython-312.pyc | Bin 0 -> 3676 bytes .../site-packages/urllib3/http2/connection.py | 356 + .../Lib/site-packages/urllib3/http2/probe.py | 87 + .../Lib/site-packages/urllib3/poolmanager.py | 637 ++ .venv/Lib/site-packages/urllib3/py.typed | 2 + .venv/Lib/site-packages/urllib3/response.py | 1278 +++ .../site-packages/urllib3/util/__init__.py | 42 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1025 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 4706 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 1237 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 0 -> 8192 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 0 -> 2896 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 0 -> 20311 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 0 -> 17175 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 0 -> 5557 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 0 -> 13325 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 0 -> 11709 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 0 -> 16240 bytes .../util/__pycache__/util.cpython-312.pyc | Bin 0 -> 2014 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 0 -> 3460 bytes .../site-packages/urllib3/util/connection.py | 137 + .venv/Lib/site-packages/urllib3/util/proxy.py | 43 + .../Lib/site-packages/urllib3/util/request.py | 258 + .../site-packages/urllib3/util/response.py | 101 + .venv/Lib/site-packages/urllib3/util/retry.py | 533 + .venv/Lib/site-packages/urllib3/util/ssl_.py | 524 + .../urllib3/util/ssl_match_hostname.py | 159 + .../urllib3/util/ssltransport.py | 271 + .../Lib/site-packages/urllib3/util/timeout.py | 275 + .venv/Lib/site-packages/urllib3/util/url.py | 469 + .venv/Lib/site-packages/urllib3/util/util.py | 42 + .venv/Lib/site-packages/urllib3/util/wait.py | 124 + .../uvicorn-0.34.2.dist-info/INSTALLER | 1 + .../uvicorn-0.34.2.dist-info/METADATA | 186 + .../uvicorn-0.34.2.dist-info/RECORD | 85 + .../uvicorn-0.34.2.dist-info/REQUESTED | 0 .../uvicorn-0.34.2.dist-info/WHEEL | 4 + .../uvicorn-0.34.2.dist-info/entry_points.txt | 2 + .../licenses/LICENSE.md | 27 + .venv/Lib/site-packages/uvicorn/__init__.py | 5 + .venv/Lib/site-packages/uvicorn/__main__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 402 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 322 bytes .../__pycache__/_subprocess.cpython-312.pyc | Bin 0 -> 2956 bytes .../__pycache__/_types.cpython-312.pyc | Bin 0 -> 11372 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 24747 bytes .../__pycache__/importer.cpython-312.pyc | Bin 0 -> 1795 bytes .../__pycache__/logging.cpython-312.pyc | Bin 0 -> 7804 bytes .../uvicorn/__pycache__/main.cpython-312.pyc | Bin 0 -> 20504 bytes .../__pycache__/server.cpython-312.pyc | Bin 0 -> 16521 bytes .../__pycache__/workers.cpython-312.pyc | Bin 0 -> 6739 bytes .../Lib/site-packages/uvicorn/_subprocess.py | 84 + .venv/Lib/site-packages/uvicorn/_types.py | 281 + .venv/Lib/site-packages/uvicorn/config.py | 530 + .venv/Lib/site-packages/uvicorn/importer.py | 34 + .../uvicorn/lifespan/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 213 bytes .../lifespan/__pycache__/off.cpython-312.pyc | Bin 0 -> 1002 bytes .../lifespan/__pycache__/on.cpython-312.pyc | Bin 0 -> 7977 bytes .../Lib/site-packages/uvicorn/lifespan/off.py | 17 + .../Lib/site-packages/uvicorn/lifespan/on.py | 137 + .venv/Lib/site-packages/uvicorn/logging.py | 117 + .../site-packages/uvicorn/loops/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 210 bytes .../loops/__pycache__/asyncio.cpython-312.pyc | Bin 0 -> 773 bytes .../loops/__pycache__/auto.cpython-312.pyc | Bin 0 -> 677 bytes .../loops/__pycache__/uvloop.cpython-312.pyc | Bin 0 -> 563 bytes .../site-packages/uvicorn/loops/asyncio.py | 10 + .venv/Lib/site-packages/uvicorn/loops/auto.py | 11 + .../Lib/site-packages/uvicorn/loops/uvloop.py | 7 + .venv/Lib/site-packages/uvicorn/main.py | 604 ++ .../uvicorn/middleware/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 215 bytes .../__pycache__/asgi2.cpython-312.pyc | Bin 0 -> 1025 bytes .../message_logger.cpython-312.pyc | Bin 0 -> 4416 bytes .../__pycache__/proxy_headers.cpython-312.pyc | Bin 0 -> 5835 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 9956 bytes .../site-packages/uvicorn/middleware/asgi2.py | 15 + .../uvicorn/middleware/message_logger.py | 87 + .../uvicorn/middleware/proxy_headers.py | 142 + .../site-packages/uvicorn/middleware/wsgi.py | 199 + .../uvicorn/protocols/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 214 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 2981 bytes .../uvicorn/protocols/http/__init__.py | 0 .../http/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 219 bytes .../http/__pycache__/auto.cpython-312.pyc | Bin 0 -> 630 bytes .../__pycache__/flow_control.cpython-312.pyc | Bin 0 -> 3060 bytes .../http/__pycache__/h11_impl.cpython-312.pyc | Bin 0 -> 27050 bytes .../httptools_impl.cpython-312.pyc | Bin 0 -> 29051 bytes .../uvicorn/protocols/http/auto.py | 15 + .../uvicorn/protocols/http/flow_control.py | 54 + .../uvicorn/protocols/http/h11_impl.py | 543 ++ .../uvicorn/protocols/http/httptools_impl.py | 570 ++ .../site-packages/uvicorn/protocols/utils.py | 56 + .../uvicorn/protocols/websockets/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 225 bytes .../__pycache__/auto.cpython-312.pyc | Bin 0 -> 810 bytes .../websockets_impl.cpython-312.pyc | Bin 0 -> 20504 bytes .../__pycache__/wsproto_impl.cpython-312.pyc | Bin 0 -> 21168 bytes .../uvicorn/protocols/websockets/auto.py | 21 + .../protocols/websockets/websockets_impl.py | 386 + .../protocols/websockets/wsproto_impl.py | 377 + .venv/Lib/site-packages/uvicorn/py.typed | 1 + .venv/Lib/site-packages/uvicorn/server.py | 337 + .../uvicorn/supervisors/__init__.py | 16 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 779 bytes .../__pycache__/basereload.cpython-312.pyc | Bin 0 -> 7072 bytes .../__pycache__/multiprocess.cpython-312.pyc | Bin 0 -> 13346 bytes .../__pycache__/statreload.cpython-312.pyc | Bin 0 -> 2866 bytes .../watchfilesreload.cpython-312.pyc | Bin 0 -> 4557 bytes .../uvicorn/supervisors/basereload.py | 126 + .../uvicorn/supervisors/multiprocess.py | 222 + .../uvicorn/supervisors/statreload.py | 53 + .../uvicorn/supervisors/watchfilesreload.py | 88 + .venv/Lib/site-packages/uvicorn/workers.py | 114 + .../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 + .venv/Lib/site-packages/werkzeug/__init__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 359 bytes .../__pycache__/_internal.cpython-312.pyc | Bin 0 -> 9782 bytes .../__pycache__/_reloader.cpython-312.pyc | Bin 0 -> 20572 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 33343 bytes .../__pycache__/formparser.cpython-312.pyc | Bin 0 -> 17040 bytes .../werkzeug/__pycache__/http.cpython-312.pyc | Bin 0 -> 50231 bytes .../__pycache__/local.cpython-312.pyc | Bin 0 -> 28483 bytes .../__pycache__/security.cpython-312.pyc | Bin 0 -> 7150 bytes .../__pycache__/serving.cpython-312.pyc | Bin 0 -> 46108 bytes .../werkzeug/__pycache__/test.cpython-312.pyc | Bin 0 -> 59831 bytes .../__pycache__/testapp.cpython-312.pyc | Bin 0 -> 8882 bytes .../werkzeug/__pycache__/urls.cpython-312.pyc | Bin 0 -> 8283 bytes .../__pycache__/user_agent.cpython-312.pyc | Bin 0 -> 2172 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 28151 bytes .../werkzeug/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 25228 bytes .venv/Lib/site-packages/werkzeug/_internal.py | 211 + .venv/Lib/site-packages/werkzeug/_reloader.py | 471 + .../werkzeug/datastructures/__init__.py | 64 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2436 bytes .../__pycache__/accept.cpython-312.pyc | Bin 0 -> 15936 bytes .../__pycache__/auth.cpython-312.pyc | Bin 0 -> 14469 bytes .../__pycache__/cache_control.cpython-312.pyc | Bin 0 -> 12242 bytes .../__pycache__/csp.cpython-312.pyc | Bin 0 -> 6205 bytes .../__pycache__/etag.cpython-312.pyc | Bin 0 -> 5429 bytes .../__pycache__/file_storage.cpython-312.pyc | Bin 0 -> 8843 bytes .../__pycache__/headers.cpython-312.pyc | Bin 0 -> 30476 bytes .../__pycache__/mixins.cpython-312.pyc | Bin 0 -> 16419 bytes .../__pycache__/range.cpython-312.pyc | Bin 0 -> 10062 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 58957 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 -> 23426 bytes .../debug/__pycache__/console.cpython-312.pyc | Bin 0 -> 11651 bytes .../debug/__pycache__/repr.cpython-312.pyc | Bin 0 -> 13793 bytes .../debug/__pycache__/tbtools.cpython-312.pyc | Bin 0 -> 16994 bytes .../site-packages/werkzeug/debug/console.py | 219 + .../Lib/site-packages/werkzeug/debug/repr.py | 282 + .../werkzeug/debug/shared/ICON_LICENSE.md | 6 + .../werkzeug/debug/shared/console.png | Bin 0 -> 507 bytes .../werkzeug/debug/shared/debugger.js | 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 + .../Lib/site-packages/werkzeug/exceptions.py | 894 ++ .../Lib/site-packages/werkzeug/formparser.py | 430 + .venv/Lib/site-packages/werkzeug/http.py | 1405 +++ .venv/Lib/site-packages/werkzeug/local.py | 653 ++ .../werkzeug/middleware/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 216 bytes .../__pycache__/dispatcher.cpython-312.pyc | Bin 0 -> 3334 bytes .../__pycache__/http_proxy.cpython-312.pyc | Bin 0 -> 9430 bytes .../__pycache__/lint.cpython-312.pyc | Bin 0 -> 17776 bytes .../__pycache__/profiler.cpython-312.pyc | Bin 0 -> 7220 bytes .../__pycache__/proxy_fix.cpython-312.pyc | Bin 0 -> 7217 bytes .../__pycache__/shared_data.cpython-312.pyc | Bin 0 -> 12752 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 + .venv/Lib/site-packages/werkzeug/py.typed | 0 .../werkzeug/routing/__init__.py | 134 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4689 bytes .../__pycache__/converters.cpython-312.pyc | Bin 0 -> 10936 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7932 bytes .../routing/__pycache__/map.cpython-312.pyc | Bin 0 -> 39817 bytes .../__pycache__/matcher.cpython-312.pyc | Bin 0 -> 8255 bytes .../routing/__pycache__/rules.cpython-312.pyc | Bin 0 -> 39101 bytes .../werkzeug/routing/converters.py | 261 + .../werkzeug/routing/exceptions.py | 152 + .../Lib/site-packages/werkzeug/routing/map.py | 951 ++ .../site-packages/werkzeug/routing/matcher.py | 202 + .../site-packages/werkzeug/routing/rules.py | 928 ++ .../site-packages/werkzeug/sansio/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 212 bytes .../sansio/__pycache__/http.cpython-312.pyc | Bin 0 -> 5658 bytes .../__pycache__/multipart.cpython-312.pyc | Bin 0 -> 14061 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 21905 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 31749 bytes .../sansio/__pycache__/utils.cpython-312.pyc | Bin 0 -> 6197 bytes .../Lib/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 + .venv/Lib/site-packages/werkzeug/security.py | 166 + .venv/Lib/site-packages/werkzeug/serving.py | 1125 +++ .venv/Lib/site-packages/werkzeug/test.py | 1464 +++ .venv/Lib/site-packages/werkzeug/testapp.py | 194 + .venv/Lib/site-packages/werkzeug/urls.py | 203 + .../Lib/site-packages/werkzeug/user_agent.py | 47 + .venv/Lib/site-packages/werkzeug/utils.py | 691 ++ .../werkzeug/wrappers/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 336 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 26145 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 34577 bytes .../werkzeug/wrappers/request.py | 650 ++ .../werkzeug/wrappers/response.py | 831 ++ .venv/Lib/site-packages/werkzeug/wsgi.py | 595 ++ .venv/Scripts/Activate.ps1 | 528 + .venv/Scripts/activate | 71 + .venv/Scripts/activate.bat | 34 + .venv/Scripts/deactivate.bat | 22 + .venv/Scripts/fastapi.exe | Bin 0 -> 108436 bytes .venv/Scripts/flask.exe | Bin 0 -> 108434 bytes .venv/Scripts/httpx.exe | Bin 0 -> 108430 bytes .venv/Scripts/normalizer.exe | Bin 0 -> 108452 bytes .venv/Scripts/pip.exe | Bin 0 -> 108447 bytes .venv/Scripts/pip3.12.exe | Bin 0 -> 108447 bytes .venv/Scripts/pip3.exe | Bin 0 -> 108447 bytes .venv/Scripts/py.test.exe | Bin 0 -> 108447 bytes .venv/Scripts/pytest.exe | Bin 0 -> 108447 bytes .venv/Scripts/python.exe | Bin 0 -> 275176 bytes .venv/Scripts/pythonw.exe | Bin 0 -> 263528 bytes .venv/Scripts/uvicorn.exe | Bin 0 -> 108437 bytes .venv/pyvenv.cfg | 5 + Design Docs/File_Tree.txt | 3097 ++++++ Design Docs/MakeTree_Put this at root.bat | 18 + Design Docs/TDD.md | 275 + SOP/Diagnostic.md | 87 + SOP/Handoff.md | 86 + custom.css | 423 + main.log | 0 main.py | 0 ...t_flask_route.cpython-312-pytest-8.3.5.pyc | Bin 0 -> 3113 bytes project/tests/test_flask_route.py | 32 + requirements.txt | 15 + 2450 files changed, 402415 insertions(+) create mode 100644 .cursor/rules/Agent_Requested.mdc create mode 100644 .cursor/rules/Always.mdc create mode 100644 .cursor/rules/Auto_Attached_Blank.mdc create mode 100644 .cursor/rules/Manual.mdc create mode 100644 .gitignore create mode 100644 .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt create mode 100644 .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/__pycache__/py.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/__pycache__/typing_extensions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__init__.py create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/_argcomplete.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/_version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/cacheprovider.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/capture.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/debugging.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/deprecated.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/doctest.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/faulthandler.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/fixtures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/freeze_support.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/helpconfig.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/hookspec.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/junitxml.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/legacypath.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/logging.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/main.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/monkeypatch.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/nodes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/outcomes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/pastebin.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/pathlib.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/pytester.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/pytester_assertions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/python.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/python_api.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/python_path.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/recwarn.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/reports.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/runner.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/scope.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/setuponly.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/setupplan.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/skipping.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/stash.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/stepwise.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/terminal.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/threadexception.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/timing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/tmpdir.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/unittest.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/unraisableexception.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/warning_types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/__pycache__/warnings.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_argcomplete.py create mode 100644 .venv/Lib/site-packages/_pytest/_code/__init__.py create mode 100644 .venv/Lib/site-packages/_pytest/_code/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_code/__pycache__/code.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_code/__pycache__/source.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_code/code.py create mode 100644 .venv/Lib/site-packages/_pytest/_code/source.py create mode 100644 .venv/Lib/site-packages/_pytest/_io/__init__.py create mode 100644 .venv/Lib/site-packages/_pytest/_io/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_io/__pycache__/pprint.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_io/__pycache__/saferepr.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_io/pprint.py create mode 100644 .venv/Lib/site-packages/_pytest/_io/saferepr.py create mode 100644 .venv/Lib/site-packages/_pytest/_io/terminalwriter.py create mode 100644 .venv/Lib/site-packages/_pytest/_io/wcwidth.py create mode 100644 .venv/Lib/site-packages/_pytest/_py/__init__.py create mode 100644 .venv/Lib/site-packages/_pytest/_py/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_py/__pycache__/error.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_py/__pycache__/path.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/_py/error.py create mode 100644 .venv/Lib/site-packages/_pytest/_py/path.py create mode 100644 .venv/Lib/site-packages/_pytest/_version.py create mode 100644 .venv/Lib/site-packages/_pytest/assertion/__init__.py create mode 100644 .venv/Lib/site-packages/_pytest/assertion/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/assertion/__pycache__/truncate.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/assertion/__pycache__/util.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/assertion/rewrite.py create mode 100644 .venv/Lib/site-packages/_pytest/assertion/truncate.py create mode 100644 .venv/Lib/site-packages/_pytest/assertion/util.py create mode 100644 .venv/Lib/site-packages/_pytest/cacheprovider.py create mode 100644 .venv/Lib/site-packages/_pytest/capture.py create mode 100644 .venv/Lib/site-packages/_pytest/compat.py create mode 100644 .venv/Lib/site-packages/_pytest/config/__init__.py create mode 100644 .venv/Lib/site-packages/_pytest/config/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/config/__pycache__/argparsing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/config/__pycache__/compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/config/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/config/__pycache__/findpaths.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/config/argparsing.py create mode 100644 .venv/Lib/site-packages/_pytest/config/compat.py create mode 100644 .venv/Lib/site-packages/_pytest/config/exceptions.py create mode 100644 .venv/Lib/site-packages/_pytest/config/findpaths.py create mode 100644 .venv/Lib/site-packages/_pytest/debugging.py create mode 100644 .venv/Lib/site-packages/_pytest/deprecated.py create mode 100644 .venv/Lib/site-packages/_pytest/doctest.py create mode 100644 .venv/Lib/site-packages/_pytest/faulthandler.py create mode 100644 .venv/Lib/site-packages/_pytest/fixtures.py create mode 100644 .venv/Lib/site-packages/_pytest/freeze_support.py create mode 100644 .venv/Lib/site-packages/_pytest/helpconfig.py create mode 100644 .venv/Lib/site-packages/_pytest/hookspec.py create mode 100644 .venv/Lib/site-packages/_pytest/junitxml.py create mode 100644 .venv/Lib/site-packages/_pytest/legacypath.py create mode 100644 .venv/Lib/site-packages/_pytest/logging.py create mode 100644 .venv/Lib/site-packages/_pytest/main.py create mode 100644 .venv/Lib/site-packages/_pytest/mark/__init__.py create mode 100644 .venv/Lib/site-packages/_pytest/mark/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/mark/__pycache__/expression.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/mark/__pycache__/structures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/_pytest/mark/expression.py create mode 100644 .venv/Lib/site-packages/_pytest/mark/structures.py create mode 100644 .venv/Lib/site-packages/_pytest/monkeypatch.py create mode 100644 .venv/Lib/site-packages/_pytest/nodes.py create mode 100644 .venv/Lib/site-packages/_pytest/outcomes.py create mode 100644 .venv/Lib/site-packages/_pytest/pastebin.py create mode 100644 .venv/Lib/site-packages/_pytest/pathlib.py create mode 100644 .venv/Lib/site-packages/_pytest/py.typed create mode 100644 .venv/Lib/site-packages/_pytest/pytester.py create mode 100644 .venv/Lib/site-packages/_pytest/pytester_assertions.py create mode 100644 .venv/Lib/site-packages/_pytest/python.py create mode 100644 .venv/Lib/site-packages/_pytest/python_api.py create mode 100644 .venv/Lib/site-packages/_pytest/python_path.py create mode 100644 .venv/Lib/site-packages/_pytest/recwarn.py create mode 100644 .venv/Lib/site-packages/_pytest/reports.py create mode 100644 .venv/Lib/site-packages/_pytest/runner.py create mode 100644 .venv/Lib/site-packages/_pytest/scope.py create mode 100644 .venv/Lib/site-packages/_pytest/setuponly.py create mode 100644 .venv/Lib/site-packages/_pytest/setupplan.py create mode 100644 .venv/Lib/site-packages/_pytest/skipping.py create mode 100644 .venv/Lib/site-packages/_pytest/stash.py create mode 100644 .venv/Lib/site-packages/_pytest/stepwise.py create mode 100644 .venv/Lib/site-packages/_pytest/terminal.py create mode 100644 .venv/Lib/site-packages/_pytest/threadexception.py create mode 100644 .venv/Lib/site-packages/_pytest/timing.py create mode 100644 .venv/Lib/site-packages/_pytest/tmpdir.py create mode 100644 .venv/Lib/site-packages/_pytest/unittest.py create mode 100644 .venv/Lib/site-packages/_pytest/unraisableexception.py create mode 100644 .venv/Lib/site-packages/_pytest/warning_types.py create mode 100644 .venv/Lib/site-packages/_pytest/warnings.py create mode 100644 .venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/annotated_types/__init__.py create mode 100644 .venv/Lib/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/annotated_types/__pycache__/test_cases.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/annotated_types/py.typed create mode 100644 .venv/Lib/site-packages/annotated_types/test_cases.py create mode 100644 .venv/Lib/site-packages/anyio-4.9.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/anyio-4.9.0.dist-info/LICENSE create mode 100644 .venv/Lib/site-packages/anyio-4.9.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/anyio-4.9.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/anyio-4.9.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/anyio-4.9.0.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/anyio-4.9.0.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/anyio/__init__.py create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/from_thread.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/lowlevel.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/pytest_plugin.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/to_interpreter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/to_process.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/to_thread.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_backends/__init__.py create mode 100644 .venv/Lib/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_backends/__pycache__/_trio.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_backends/_asyncio.py create mode 100644 .venv/Lib/site-packages/anyio/_backends/_trio.py create mode 100644 .venv/Lib/site-packages/anyio/_core/__init__.py create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_fileio.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_resources.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_signals.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_sockets.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_streams.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_tasks.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_testing.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/_core/_asyncio_selector_thread.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_eventloop.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_exceptions.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_fileio.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_resources.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_signals.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_sockets.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_streams.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_subprocesses.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_synchronization.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_tasks.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_tempfile.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_testing.py create mode 100644 .venv/Lib/site-packages/anyio/_core/_typedattr.py create mode 100644 .venv/Lib/site-packages/anyio/abc/__init__.py create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_eventloop.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_eventloop.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_resources.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_sockets.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_streams.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_tasks.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_testing.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/abc/_eventloop.py create mode 100644 .venv/Lib/site-packages/anyio/abc/_resources.py create mode 100644 .venv/Lib/site-packages/anyio/abc/_sockets.py create mode 100644 .venv/Lib/site-packages/anyio/abc/_streams.py create mode 100644 .venv/Lib/site-packages/anyio/abc/_subprocesses.py create mode 100644 .venv/Lib/site-packages/anyio/abc/_tasks.py create mode 100644 .venv/Lib/site-packages/anyio/abc/_testing.py create mode 100644 .venv/Lib/site-packages/anyio/from_thread.py create mode 100644 .venv/Lib/site-packages/anyio/lowlevel.py create mode 100644 .venv/Lib/site-packages/anyio/py.typed create mode 100644 .venv/Lib/site-packages/anyio/pytest_plugin.py create mode 100644 .venv/Lib/site-packages/anyio/streams/__init__.py create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/file.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/memory.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/stapled.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/text.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/tls.cpython-312-pytest-8.3.5.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/anyio/streams/buffered.py create mode 100644 .venv/Lib/site-packages/anyio/streams/file.py create mode 100644 .venv/Lib/site-packages/anyio/streams/memory.py create mode 100644 .venv/Lib/site-packages/anyio/streams/stapled.py create mode 100644 .venv/Lib/site-packages/anyio/streams/text.py create mode 100644 .venv/Lib/site-packages/anyio/streams/tls.py create mode 100644 .venv/Lib/site-packages/anyio/to_interpreter.py create mode 100644 .venv/Lib/site-packages/anyio/to_process.py create mode 100644 .venv/Lib/site-packages/anyio/to_thread.py create mode 100644 .venv/Lib/site-packages/blinker-1.9.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/blinker-1.9.0.dist-info/LICENSE.txt create mode 100644 .venv/Lib/site-packages/blinker-1.9.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/blinker-1.9.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/blinker-1.9.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/blinker/__init__.py create mode 100644 .venv/Lib/site-packages/blinker/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/blinker/__pycache__/_utilities.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/blinker/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/blinker/_utilities.py create mode 100644 .venv/Lib/site-packages/blinker/base.py create mode 100644 .venv/Lib/site-packages/blinker/py.typed create mode 100644 .venv/Lib/site-packages/certifi-2025.4.26.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/certifi-2025.4.26.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/certifi-2025.4.26.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/certifi-2025.4.26.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/certifi-2025.4.26.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/certifi-2025.4.26.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/certifi/__init__.py create mode 100644 .venv/Lib/site-packages/certifi/__main__.py create mode 100644 .venv/Lib/site-packages/certifi/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/certifi/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/certifi/__pycache__/core.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/certifi/core.py create mode 100644 .venv/Lib/site-packages/certifi/py.typed create mode 100644 .venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/charset_normalizer/__init__.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/__main__.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/md.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/api.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/cd.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/cli/__init__.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/cli/__main__.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/cli/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/charset_normalizer/constant.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/legacy.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/md.cp312-win_amd64.pyd create mode 100644 .venv/Lib/site-packages/charset_normalizer/md.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/md__mypyc.cp312-win_amd64.pyd create mode 100644 .venv/Lib/site-packages/charset_normalizer/models.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/py.typed create mode 100644 .venv/Lib/site-packages/charset_normalizer/utils.py create mode 100644 .venv/Lib/site-packages/charset_normalizer/version.py create mode 100644 .venv/Lib/site-packages/click-8.2.1.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/click-8.2.1.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/click-8.2.1.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/click-8.2.1.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/click-8.2.1.dist-info/licenses/LICENSE.txt create mode 100644 .venv/Lib/site-packages/click/__init__.py create mode 100644 .venv/Lib/site-packages/click/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/_compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/core.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/decorators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/formatting.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/globals.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/parser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/termui.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/testing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/click/_compat.py create mode 100644 .venv/Lib/site-packages/click/_termui_impl.py create mode 100644 .venv/Lib/site-packages/click/_textwrap.py create mode 100644 .venv/Lib/site-packages/click/_winconsole.py create mode 100644 .venv/Lib/site-packages/click/core.py create mode 100644 .venv/Lib/site-packages/click/decorators.py create mode 100644 .venv/Lib/site-packages/click/exceptions.py create mode 100644 .venv/Lib/site-packages/click/formatting.py create mode 100644 .venv/Lib/site-packages/click/globals.py create mode 100644 .venv/Lib/site-packages/click/parser.py create mode 100644 .venv/Lib/site-packages/click/py.typed create mode 100644 .venv/Lib/site-packages/click/shell_completion.py create mode 100644 .venv/Lib/site-packages/click/termui.py create mode 100644 .venv/Lib/site-packages/click/testing.py create mode 100644 .venv/Lib/site-packages/click/types.py create mode 100644 .venv/Lib/site-packages/click/utils.py create mode 100644 .venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt create mode 100644 .venv/Lib/site-packages/colorama/__init__.py create mode 100644 .venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/__pycache__/win32.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/ansi.py create mode 100644 .venv/Lib/site-packages/colorama/ansitowin32.py create mode 100644 .venv/Lib/site-packages/colorama/initialise.py create mode 100644 .venv/Lib/site-packages/colorama/tests/__init__.py create mode 100644 .venv/Lib/site-packages/colorama/tests/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/tests/__pycache__/ansi_test.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/tests/__pycache__/initialise_test.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/tests/__pycache__/isatty_test.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/tests/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/tests/__pycache__/winterm_test.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/colorama/tests/ansi_test.py create mode 100644 .venv/Lib/site-packages/colorama/tests/ansitowin32_test.py create mode 100644 .venv/Lib/site-packages/colorama/tests/initialise_test.py create mode 100644 .venv/Lib/site-packages/colorama/tests/isatty_test.py create mode 100644 .venv/Lib/site-packages/colorama/tests/utils.py create mode 100644 .venv/Lib/site-packages/colorama/tests/winterm_test.py create mode 100644 .venv/Lib/site-packages/colorama/win32.py create mode 100644 .venv/Lib/site-packages/colorama/winterm.py create mode 100644 .venv/Lib/site-packages/fastapi-0.115.12.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/fastapi-0.115.12.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/fastapi-0.115.12.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/fastapi-0.115.12.dist-info/REQUESTED create mode 100644 .venv/Lib/site-packages/fastapi-0.115.12.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/fastapi-0.115.12.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/fastapi-0.115.12.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/fastapi/__init__.py create mode 100644 .venv/Lib/site-packages/fastapi/__main__.py create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/_compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/applications.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/background.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/cli.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/logger.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/params.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/requests.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/responses.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/routing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/templating.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/testclient.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/_compat.py create mode 100644 .venv/Lib/site-packages/fastapi/applications.py create mode 100644 .venv/Lib/site-packages/fastapi/background.py create mode 100644 .venv/Lib/site-packages/fastapi/cli.py create mode 100644 .venv/Lib/site-packages/fastapi/concurrency.py create mode 100644 .venv/Lib/site-packages/fastapi/datastructures.py create mode 100644 .venv/Lib/site-packages/fastapi/dependencies/__init__.py create mode 100644 .venv/Lib/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/dependencies/models.py create mode 100644 .venv/Lib/site-packages/fastapi/dependencies/utils.py create mode 100644 .venv/Lib/site-packages/fastapi/encoders.py create mode 100644 .venv/Lib/site-packages/fastapi/exception_handlers.py create mode 100644 .venv/Lib/site-packages/fastapi/exceptions.py create mode 100644 .venv/Lib/site-packages/fastapi/logger.py create mode 100644 .venv/Lib/site-packages/fastapi/middleware/__init__.py create mode 100644 .venv/Lib/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/middleware/__pycache__/gzip.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/middleware/__pycache__/trustedhost.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/middleware/__pycache__/wsgi.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/middleware/cors.py create mode 100644 .venv/Lib/site-packages/fastapi/middleware/gzip.py create mode 100644 .venv/Lib/site-packages/fastapi/middleware/httpsredirect.py create mode 100644 .venv/Lib/site-packages/fastapi/middleware/trustedhost.py create mode 100644 .venv/Lib/site-packages/fastapi/middleware/wsgi.py create mode 100644 .venv/Lib/site-packages/fastapi/openapi/__init__.py create mode 100644 .venv/Lib/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/openapi/constants.py create mode 100644 .venv/Lib/site-packages/fastapi/openapi/docs.py create mode 100644 .venv/Lib/site-packages/fastapi/openapi/models.py create mode 100644 .venv/Lib/site-packages/fastapi/openapi/utils.py create mode 100644 .venv/Lib/site-packages/fastapi/param_functions.py create mode 100644 .venv/Lib/site-packages/fastapi/params.py create mode 100644 .venv/Lib/site-packages/fastapi/py.typed create mode 100644 .venv/Lib/site-packages/fastapi/requests.py create mode 100644 .venv/Lib/site-packages/fastapi/responses.py create mode 100644 .venv/Lib/site-packages/fastapi/routing.py create mode 100644 .venv/Lib/site-packages/fastapi/security/__init__.py create mode 100644 .venv/Lib/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/fastapi/security/api_key.py create mode 100644 .venv/Lib/site-packages/fastapi/security/base.py create mode 100644 .venv/Lib/site-packages/fastapi/security/http.py create mode 100644 .venv/Lib/site-packages/fastapi/security/oauth2.py create mode 100644 .venv/Lib/site-packages/fastapi/security/open_id_connect_url.py create mode 100644 .venv/Lib/site-packages/fastapi/security/utils.py create mode 100644 .venv/Lib/site-packages/fastapi/staticfiles.py create mode 100644 .venv/Lib/site-packages/fastapi/templating.py create mode 100644 .venv/Lib/site-packages/fastapi/testclient.py create mode 100644 .venv/Lib/site-packages/fastapi/types.py create mode 100644 .venv/Lib/site-packages/fastapi/utils.py create mode 100644 .venv/Lib/site-packages/fastapi/websockets.py create mode 100644 .venv/Lib/site-packages/flask-3.1.1.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/flask-3.1.1.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/flask-3.1.1.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/flask-3.1.1.dist-info/REQUESTED create mode 100644 .venv/Lib/site-packages/flask-3.1.1.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/flask-3.1.1.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/flask-3.1.1.dist-info/licenses/LICENSE.txt create mode 100644 .venv/Lib/site-packages/flask/__init__.py create mode 100644 .venv/Lib/site-packages/flask/__main__.py create mode 100644 .venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/app.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/config.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/globals.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/logging.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/sessions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/signals.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/templating.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/views.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/app.py create mode 100644 .venv/Lib/site-packages/flask/blueprints.py create mode 100644 .venv/Lib/site-packages/flask/cli.py create mode 100644 .venv/Lib/site-packages/flask/config.py create mode 100644 .venv/Lib/site-packages/flask/ctx.py create mode 100644 .venv/Lib/site-packages/flask/debughelpers.py create mode 100644 .venv/Lib/site-packages/flask/globals.py create mode 100644 .venv/Lib/site-packages/flask/helpers.py create mode 100644 .venv/Lib/site-packages/flask/json/__init__.py create mode 100644 .venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/json/provider.py create mode 100644 .venv/Lib/site-packages/flask/json/tag.py create mode 100644 .venv/Lib/site-packages/flask/logging.py create mode 100644 .venv/Lib/site-packages/flask/py.typed create mode 100644 .venv/Lib/site-packages/flask/sansio/README.md create mode 100644 .venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/flask/sansio/app.py create mode 100644 .venv/Lib/site-packages/flask/sansio/blueprints.py create mode 100644 .venv/Lib/site-packages/flask/sansio/scaffold.py create mode 100644 .venv/Lib/site-packages/flask/sessions.py create mode 100644 .venv/Lib/site-packages/flask/signals.py create mode 100644 .venv/Lib/site-packages/flask/templating.py create mode 100644 .venv/Lib/site-packages/flask/testing.py create mode 100644 .venv/Lib/site-packages/flask/typing.py create mode 100644 .venv/Lib/site-packages/flask/views.py create mode 100644 .venv/Lib/site-packages/flask/wrappers.py create mode 100644 .venv/Lib/site-packages/h11-0.16.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/h11-0.16.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/h11-0.16.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/h11-0.16.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt create mode 100644 .venv/Lib/site-packages/h11-0.16.0.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/h11/__init__.py create mode 100644 .venv/Lib/site-packages/h11/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_abnf.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_events.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_headers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_readers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_state.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_util.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/__pycache__/_writers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/h11/_abnf.py create mode 100644 .venv/Lib/site-packages/h11/_connection.py create mode 100644 .venv/Lib/site-packages/h11/_events.py create mode 100644 .venv/Lib/site-packages/h11/_headers.py create mode 100644 .venv/Lib/site-packages/h11/_readers.py create mode 100644 .venv/Lib/site-packages/h11/_receivebuffer.py create mode 100644 .venv/Lib/site-packages/h11/_state.py create mode 100644 .venv/Lib/site-packages/h11/_util.py create mode 100644 .venv/Lib/site-packages/h11/_version.py create mode 100644 .venv/Lib/site-packages/h11/_writers.py create mode 100644 .venv/Lib/site-packages/h11/py.typed create mode 100644 .venv/Lib/site-packages/httpcore-1.0.9.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/httpcore-1.0.9.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/httpcore-1.0.9.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/httpcore-1.0.9.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md create mode 100644 .venv/Lib/site-packages/httpcore/__init__.py create mode 100644 .venv/Lib/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/__pycache__/_api.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/__pycache__/_exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/__pycache__/_models.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/__pycache__/_ssl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/__pycache__/_trace.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_api.py create mode 100644 .venv/Lib/site-packages/httpcore/_async/__init__.py create mode 100644 .venv/Lib/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_async/__pycache__/connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_async/__pycache__/http2.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_async/connection.py create mode 100644 .venv/Lib/site-packages/httpcore/_async/connection_pool.py create mode 100644 .venv/Lib/site-packages/httpcore/_async/http11.py create mode 100644 .venv/Lib/site-packages/httpcore/_async/http2.py create mode 100644 .venv/Lib/site-packages/httpcore/_async/http_proxy.py create mode 100644 .venv/Lib/site-packages/httpcore/_async/interfaces.py create mode 100644 .venv/Lib/site-packages/httpcore/_async/socks_proxy.py create mode 100644 .venv/Lib/site-packages/httpcore/_backends/__init__.py create mode 100644 .venv/Lib/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_backends/__pycache__/trio.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_backends/anyio.py create mode 100644 .venv/Lib/site-packages/httpcore/_backends/auto.py create mode 100644 .venv/Lib/site-packages/httpcore/_backends/base.py create mode 100644 .venv/Lib/site-packages/httpcore/_backends/mock.py create mode 100644 .venv/Lib/site-packages/httpcore/_backends/sync.py create mode 100644 .venv/Lib/site-packages/httpcore/_backends/trio.py create mode 100644 .venv/Lib/site-packages/httpcore/_exceptions.py create mode 100644 .venv/Lib/site-packages/httpcore/_models.py create mode 100644 .venv/Lib/site-packages/httpcore/_ssl.py create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__init__.py create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__pycache__/http11.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpcore/_sync/connection.py create mode 100644 .venv/Lib/site-packages/httpcore/_sync/connection_pool.py create mode 100644 .venv/Lib/site-packages/httpcore/_sync/http11.py create mode 100644 .venv/Lib/site-packages/httpcore/_sync/http2.py create mode 100644 .venv/Lib/site-packages/httpcore/_sync/http_proxy.py create mode 100644 .venv/Lib/site-packages/httpcore/_sync/interfaces.py create mode 100644 .venv/Lib/site-packages/httpcore/_sync/socks_proxy.py create mode 100644 .venv/Lib/site-packages/httpcore/_synchronization.py create mode 100644 .venv/Lib/site-packages/httpcore/_trace.py create mode 100644 .venv/Lib/site-packages/httpcore/_utils.py create mode 100644 .venv/Lib/site-packages/httpcore/py.typed create mode 100644 .venv/Lib/site-packages/httpx-0.28.1.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/httpx-0.28.1.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/httpx-0.28.1.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/httpx-0.28.1.dist-info/REQUESTED create mode 100644 .venv/Lib/site-packages/httpx-0.28.1.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/httpx-0.28.1.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md create mode 100644 .venv/Lib/site-packages/httpx/__init__.py create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/__version__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_api.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_auth.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_client.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_config.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_content.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_main.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_models.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_urls.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__pycache__/_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/__version__.py create mode 100644 .venv/Lib/site-packages/httpx/_api.py create mode 100644 .venv/Lib/site-packages/httpx/_auth.py create mode 100644 .venv/Lib/site-packages/httpx/_client.py create mode 100644 .venv/Lib/site-packages/httpx/_config.py create mode 100644 .venv/Lib/site-packages/httpx/_content.py create mode 100644 .venv/Lib/site-packages/httpx/_decoders.py create mode 100644 .venv/Lib/site-packages/httpx/_exceptions.py create mode 100644 .venv/Lib/site-packages/httpx/_main.py create mode 100644 .venv/Lib/site-packages/httpx/_models.py create mode 100644 .venv/Lib/site-packages/httpx/_multipart.py create mode 100644 .venv/Lib/site-packages/httpx/_status_codes.py create mode 100644 .venv/Lib/site-packages/httpx/_transports/__init__.py create mode 100644 .venv/Lib/site-packages/httpx/_transports/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/_transports/__pycache__/asgi.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/_transports/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/_transports/__pycache__/default.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/_transports/__pycache__/mock.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/_transports/__pycache__/wsgi.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/httpx/_transports/asgi.py create mode 100644 .venv/Lib/site-packages/httpx/_transports/base.py create mode 100644 .venv/Lib/site-packages/httpx/_transports/default.py create mode 100644 .venv/Lib/site-packages/httpx/_transports/mock.py create mode 100644 .venv/Lib/site-packages/httpx/_transports/wsgi.py create mode 100644 .venv/Lib/site-packages/httpx/_types.py create mode 100644 .venv/Lib/site-packages/httpx/_urlparse.py create mode 100644 .venv/Lib/site-packages/httpx/_urls.py create mode 100644 .venv/Lib/site-packages/httpx/_utils.py create mode 100644 .venv/Lib/site-packages/httpx/py.typed create mode 100644 .venv/Lib/site-packages/idna-3.10.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/idna-3.10.dist-info/LICENSE.md create mode 100644 .venv/Lib/site-packages/idna-3.10.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/idna-3.10.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/idna-3.10.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/idna/__init__.py create mode 100644 .venv/Lib/site-packages/idna/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/idna/__pycache__/codec.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/idna/__pycache__/compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/idna/__pycache__/core.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/idna/__pycache__/idnadata.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/idna/__pycache__/intranges.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/idna/__pycache__/package_data.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/idna/__pycache__/uts46data.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/idna/codec.py create mode 100644 .venv/Lib/site-packages/idna/compat.py create mode 100644 .venv/Lib/site-packages/idna/core.py create mode 100644 .venv/Lib/site-packages/idna/idnadata.py create mode 100644 .venv/Lib/site-packages/idna/intranges.py create mode 100644 .venv/Lib/site-packages/idna/package_data.py create mode 100644 .venv/Lib/site-packages/idna/py.typed create mode 100644 .venv/Lib/site-packages/idna/uts46data.py create mode 100644 .venv/Lib/site-packages/iniconfig-2.1.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/iniconfig-2.1.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/iniconfig-2.1.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/iniconfig-2.1.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/iniconfig-2.1.0.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/iniconfig/__init__.py create mode 100644 .venv/Lib/site-packages/iniconfig/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/iniconfig/__pycache__/_parse.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/iniconfig/__pycache__/_version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/iniconfig/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/iniconfig/_parse.py create mode 100644 .venv/Lib/site-packages/iniconfig/_version.py create mode 100644 .venv/Lib/site-packages/iniconfig/exceptions.py create mode 100644 .venv/Lib/site-packages/iniconfig/py.typed create mode 100644 .venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt create mode 100644 .venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/itsdangerous/__init__.py create mode 100644 .venv/Lib/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/itsdangerous/__pycache__/_json.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/itsdangerous/__pycache__/exc.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/itsdangerous/__pycache__/serializer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/itsdangerous/__pycache__/timed.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/itsdangerous/__pycache__/url_safe.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/itsdangerous/_json.py create mode 100644 .venv/Lib/site-packages/itsdangerous/encoding.py create mode 100644 .venv/Lib/site-packages/itsdangerous/exc.py create mode 100644 .venv/Lib/site-packages/itsdangerous/py.typed create mode 100644 .venv/Lib/site-packages/itsdangerous/serializer.py create mode 100644 .venv/Lib/site-packages/itsdangerous/signer.py create mode 100644 .venv/Lib/site-packages/itsdangerous/timed.py create mode 100644 .venv/Lib/site-packages/itsdangerous/url_safe.py create mode 100644 .venv/Lib/site-packages/jinja2-3.1.6.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/jinja2-3.1.6.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/jinja2-3.1.6.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/jinja2-3.1.6.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/jinja2-3.1.6.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt create mode 100644 .venv/Lib/site-packages/jinja2/__init__.py create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/constants.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/debug.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/environment.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/ext.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/filters.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/idtracking.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/loaders.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/meta.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/nodes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/parser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/runtime.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/tests.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/jinja2/_identifier.py create mode 100644 .venv/Lib/site-packages/jinja2/async_utils.py create mode 100644 .venv/Lib/site-packages/jinja2/bccache.py create mode 100644 .venv/Lib/site-packages/jinja2/compiler.py create mode 100644 .venv/Lib/site-packages/jinja2/constants.py create mode 100644 .venv/Lib/site-packages/jinja2/debug.py create mode 100644 .venv/Lib/site-packages/jinja2/defaults.py create mode 100644 .venv/Lib/site-packages/jinja2/environment.py create mode 100644 .venv/Lib/site-packages/jinja2/exceptions.py create mode 100644 .venv/Lib/site-packages/jinja2/ext.py create mode 100644 .venv/Lib/site-packages/jinja2/filters.py create mode 100644 .venv/Lib/site-packages/jinja2/idtracking.py create mode 100644 .venv/Lib/site-packages/jinja2/lexer.py create mode 100644 .venv/Lib/site-packages/jinja2/loaders.py create mode 100644 .venv/Lib/site-packages/jinja2/meta.py create mode 100644 .venv/Lib/site-packages/jinja2/nativetypes.py create mode 100644 .venv/Lib/site-packages/jinja2/nodes.py create mode 100644 .venv/Lib/site-packages/jinja2/optimizer.py create mode 100644 .venv/Lib/site-packages/jinja2/parser.py create mode 100644 .venv/Lib/site-packages/jinja2/py.typed create mode 100644 .venv/Lib/site-packages/jinja2/runtime.py create mode 100644 .venv/Lib/site-packages/jinja2/sandbox.py create mode 100644 .venv/Lib/site-packages/jinja2/tests.py create mode 100644 .venv/Lib/site-packages/jinja2/utils.py create mode 100644 .venv/Lib/site-packages/jinja2/visitor.py create mode 100644 .venv/Lib/site-packages/markupsafe/__init__.py create mode 100644 .venv/Lib/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/markupsafe/__pycache__/_native.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/markupsafe/_native.py create mode 100644 .venv/Lib/site-packages/markupsafe/_speedups.c create mode 100644 .venv/Lib/site-packages/markupsafe/_speedups.cp312-win_amd64.pyd create mode 100644 .venv/Lib/site-packages/markupsafe/_speedups.pyi create mode 100644 .venv/Lib/site-packages/markupsafe/py.typed create mode 100644 .venv/Lib/site-packages/packaging-25.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/packaging-25.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/packaging-25.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/packaging-25.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/packaging-25.0.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/packaging-25.0.dist-info/licenses/LICENSE.APACHE create mode 100644 .venv/Lib/site-packages/packaging-25.0.dist-info/licenses/LICENSE.BSD create mode 100644 .venv/Lib/site-packages/packaging/__init__.py create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/_elffile.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/_manylinux.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/_musllinux.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/_parser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/_structures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/_tokenizer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/markers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/metadata.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/requirements.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/specifiers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/tags.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/__pycache__/version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/_elffile.py create mode 100644 .venv/Lib/site-packages/packaging/_manylinux.py create mode 100644 .venv/Lib/site-packages/packaging/_musllinux.py create mode 100644 .venv/Lib/site-packages/packaging/_parser.py create mode 100644 .venv/Lib/site-packages/packaging/_structures.py create mode 100644 .venv/Lib/site-packages/packaging/_tokenizer.py create mode 100644 .venv/Lib/site-packages/packaging/licenses/__init__.py create mode 100644 .venv/Lib/site-packages/packaging/licenses/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/licenses/__pycache__/_spdx.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/packaging/licenses/_spdx.py create mode 100644 .venv/Lib/site-packages/packaging/markers.py create mode 100644 .venv/Lib/site-packages/packaging/metadata.py create mode 100644 .venv/Lib/site-packages/packaging/py.typed create mode 100644 .venv/Lib/site-packages/packaging/requirements.py create mode 100644 .venv/Lib/site-packages/packaging/specifiers.py create mode 100644 .venv/Lib/site-packages/packaging/tags.py create mode 100644 .venv/Lib/site-packages/packaging/utils.py create mode 100644 .venv/Lib/site-packages/packaging/version.py create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/AUTHORS.txt create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/LICENSE.txt create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/REQUESTED create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/pip-24.3.1.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/pip/__init__.py create mode 100644 .venv/Lib/site-packages/pip/__main__.py create mode 100644 .venv/Lib/site-packages/pip/__pip-runner__.py create mode 100644 .venv/Lib/site-packages/pip/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/main.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/build_env.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cache.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/base_command.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/command_context.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/index_command.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/main.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/main_parser.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/parser.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/req_command.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/spinners.py create mode 100644 .venv/Lib/site-packages/pip/_internal/cli/status_codes.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/cache.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/check.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/completion.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/debug.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/download.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/hash.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/help.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/index.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/search.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/cache.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/check.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/completion.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/configuration.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/debug.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/download.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/freeze.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/hash.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/help.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/index.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/inspect.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/install.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/list.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/search.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/show.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/uninstall.py create mode 100644 .venv/Lib/site-packages/pip/_internal/commands/wheel.py create mode 100644 .venv/Lib/site-packages/pip/_internal/configuration.py create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/base.py create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/installed.py create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/sdist.py create mode 100644 .venv/Lib/site-packages/pip/_internal/distributions/wheel.py create mode 100644 .venv/Lib/site-packages/pip/_internal/exceptions.py create mode 100644 .venv/Lib/site-packages/pip/_internal/index/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/index/collector.py create mode 100644 .venv/Lib/site-packages/pip/_internal/index/package_finder.py create mode 100644 .venv/Lib/site-packages/pip/_internal/index/sources.py create mode 100644 .venv/Lib/site-packages/pip/_internal/locations/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/locations/_distutils.py create mode 100644 .venv/Lib/site-packages/pip/_internal/locations/_sysconfig.py create mode 100644 .venv/Lib/site-packages/pip/_internal/locations/base.py create mode 100644 .venv/Lib/site-packages/pip/_internal/main.py create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/_json.py create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/base.py create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/importlib/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/importlib/_compat.py create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py create mode 100644 .venv/Lib/site-packages/pip/_internal/metadata/pkg_resources.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/models/candidate.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/direct_url.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/format_control.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/index.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/installation_report.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/link.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/scheme.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/search_scope.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/target_python.py create mode 100644 .venv/Lib/site-packages/pip/_internal/models/wheel.py create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/network/auth.py create mode 100644 .venv/Lib/site-packages/pip/_internal/network/cache.py create mode 100644 .venv/Lib/site-packages/pip/_internal/network/download.py create mode 100644 .venv/Lib/site-packages/pip/_internal/network/lazy_wheel.py create mode 100644 .venv/Lib/site-packages/pip/_internal/network/session.py create mode 100644 .venv/Lib/site-packages/pip/_internal/network/utils.py create mode 100644 .venv/Lib/site-packages/pip/_internal/network/xmlrpc.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/build_tracker.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/metadata_editable.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/wheel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/build_tracker.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/metadata.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/metadata_editable.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/metadata_legacy.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/wheel.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/wheel_editable.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/build/wheel_legacy.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/check.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/freeze.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/install/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/install/__pycache__/editable_legacy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/install/editable_legacy.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/install/wheel.py create mode 100644 .venv/Lib/site-packages/pip/_internal/operations/prepare.py create mode 100644 .venv/Lib/site-packages/pip/_internal/pyproject.py create mode 100644 .venv/Lib/site-packages/pip/_internal/req/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/req/constructors.py create mode 100644 .venv/Lib/site-packages/pip/_internal/req/req_file.py create mode 100644 .venv/Lib/site-packages/pip/_internal/req/req_install.py create mode 100644 .venv/Lib/site-packages/pip/_internal/req/req_set.py create mode 100644 .venv/Lib/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/base.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/legacy/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/base.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/candidates.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/factory.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/provider.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/reporter.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/requirements.py create mode 100644 .venv/Lib/site-packages/pip/_internal/resolution/resolvelib/resolver.py create mode 100644 .venv/Lib/site-packages/pip/_internal/self_outdated_check.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/retry.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/_jaraco_text.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/_log.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/appdirs.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/compat.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/compatibility_tags.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/datetime.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/deprecation.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/direct_url_helpers.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/egg_link.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/encoding.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/entrypoints.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/filesystem.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/filetypes.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/glibc.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/hashes.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/logging.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/misc.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/packaging.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/retry.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/subprocess.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/unpacking.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/urls.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 .venv/Lib/site-packages/pip/_internal/utils/wheel.py create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/git.py create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/subversion.py create mode 100644 .venv/Lib/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 .venv/Lib/site-packages/pip/_internal/wheel_builder.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/__pycache__/typing_extensions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/certifi/core.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/certifi/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/locators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/manifest.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/metadata.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/compat.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/database.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/index.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/locators.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/markers.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/resources.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/t32.exe create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/t64-arm.exe create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/t64.exe create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/util.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/version.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/w32.exe create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/w64-arm.exe create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/w64.exe create mode 100644 .venv/Lib/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distro/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distro/__main__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/distro/distro.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/distro/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/codec.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/compat.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/core.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/intranges.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/package_data.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/msgpack/ext.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/_elffile.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/_manylinux.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/_musllinux.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/_parser.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/_tokenizer.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/markers.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/metadata.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/tags.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/utils.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/packaging/version.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__main__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/android.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/api.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/macos.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/unix.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/version.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/platformdirs/windows.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__main__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/cmdline.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/console.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/filter.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/filters/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatter.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/_mapping.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/bbcode.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/groff.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/html.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/img.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/irc.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/latex.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/other.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/rtf.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/svg.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal256.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/lexer.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/lexers/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/lexers/_mapping.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/lexers/python.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/modeline.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/plugin.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/regexopt.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/scanner.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/sphinxext.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/style.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/styles/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/styles/_mapping.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/token.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/unistring.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pygments/util.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_compat.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_impl.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/help.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/__version__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/adapters.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/api.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/auth.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/certs.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/compat.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/cookies.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/help.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/hooks.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/models.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/packages.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/sessions.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/structures.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/requests/utils.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/resolvers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/providers.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/reporters.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/resolvers.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/resolvelib/structs.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__main__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/json.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/status.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_cell_widths.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_emoji_codes.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_emoji_replace.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_export_format.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_extension.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_fileno.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_inspect.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_log_render.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_loop.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_null_file.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_palettes.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_pick.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_ratio.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_spinners.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_stack.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_timer.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_win32_console.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_windows.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_windows_renderer.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/_wrap.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/abc.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/align.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/ansi.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/bar.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/box.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/cells.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/color.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/color_triplet.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/columns.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/console.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/constrain.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/containers.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/control.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/default_styles.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/diagnose.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/emoji.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/errors.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/file_proxy.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/filesize.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/highlighter.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/json.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/jupyter.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/layout.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/live.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/live_render.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/logging.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/markup.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/measure.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/padding.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/pager.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/palette.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/panel.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/pretty.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/progress.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/progress_bar.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/prompt.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/protocol.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/region.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/repr.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/rule.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/scope.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/screen.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/segment.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/spinner.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/status.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/style.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/styled.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/syntax.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/table.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/terminal_theme.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/text.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/theme.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/themes.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/traceback.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/rich/tree.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/_parser.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/_re.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/_types.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/tomli/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/_api.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/_macos.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/_openssl.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/_ssl_constants.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/_windows.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/truststore/py.typed create mode 100644 .venv/Lib/site-packages/pip/_vendor/typing_extensions.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/_version.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/weakref_finalize.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/request.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/response.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/proxy.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/ssltransport.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 .venv/Lib/site-packages/pip/_vendor/vendor.txt create mode 100644 .venv/Lib/site-packages/pip/py.typed create mode 100644 .venv/Lib/site-packages/pluggy-1.6.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/pluggy-1.6.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/pluggy-1.6.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/pluggy-1.6.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/pluggy-1.6.0.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/pluggy-1.6.0.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/pluggy/__init__.py create mode 100644 .venv/Lib/site-packages/pluggy/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pluggy/__pycache__/_callers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pluggy/__pycache__/_hooks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pluggy/__pycache__/_manager.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pluggy/__pycache__/_result.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pluggy/__pycache__/_tracing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pluggy/__pycache__/_version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pluggy/__pycache__/_warnings.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pluggy/_callers.py create mode 100644 .venv/Lib/site-packages/pluggy/_hooks.py create mode 100644 .venv/Lib/site-packages/pluggy/_manager.py create mode 100644 .venv/Lib/site-packages/pluggy/_result.py create mode 100644 .venv/Lib/site-packages/pluggy/_tracing.py create mode 100644 .venv/Lib/site-packages/pluggy/_version.py create mode 100644 .venv/Lib/site-packages/pluggy/_warnings.py create mode 100644 .venv/Lib/site-packages/pluggy/py.typed create mode 100644 .venv/Lib/site-packages/py.py create mode 100644 .venv/Lib/site-packages/pydantic-2.11.5.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/pydantic-2.11.5.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/pydantic-2.11.5.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/pydantic-2.11.5.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/pydantic-2.11.5.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/pydantic/__init__.py create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/_migration.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/alias_generators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/aliases.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/annotated_handlers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/class_validators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/color.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/config.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/dataclasses.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/datetime_parse.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/decorator.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/env_settings.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/error_wrappers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/errors.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/fields.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/functional_serializers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/functional_validators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/generics.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/json.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/json_schema.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/main.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/mypy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/networks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/parse.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/root_model.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/schema.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/tools.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/type_adapter.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/typing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/validate_call_decorator.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/validators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/__pycache__/warnings.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__init__.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_config.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_core_metadata.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_core_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_dataclasses.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_decorators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_decorators_v1.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_discriminated_union.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_docs_extraction.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_fields.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_forward_ref.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_generate_schema.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_generics.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_git.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_import_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_internal_dataclass.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_known_annotated_metadata.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_mock_val_ser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_model_construction.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_namespace_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_repr.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_schema_gather.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_schema_generation_shared.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_serializers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_signature.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_typing_extra.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_validate_call.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/__pycache__/_validators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_config.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_core_metadata.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_core_utils.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_dataclasses.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_decorators.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_decorators_v1.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_discriminated_union.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_docs_extraction.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_fields.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_forward_ref.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_generate_schema.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_generics.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_git.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_import_utils.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_internal_dataclass.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_known_annotated_metadata.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_mock_val_ser.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_model_construction.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_namespace_utils.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_repr.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_schema_gather.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_schema_generation_shared.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_serializers.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_signature.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_typing_extra.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_utils.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_validate_call.py create mode 100644 .venv/Lib/site-packages/pydantic/_internal/_validators.py create mode 100644 .venv/Lib/site-packages/pydantic/_migration.py create mode 100644 .venv/Lib/site-packages/pydantic/alias_generators.py create mode 100644 .venv/Lib/site-packages/pydantic/aliases.py create mode 100644 .venv/Lib/site-packages/pydantic/annotated_handlers.py create mode 100644 .venv/Lib/site-packages/pydantic/class_validators.py create mode 100644 .venv/Lib/site-packages/pydantic/color.py create mode 100644 .venv/Lib/site-packages/pydantic/config.py create mode 100644 .venv/Lib/site-packages/pydantic/dataclasses.py create mode 100644 .venv/Lib/site-packages/pydantic/datetime_parse.py create mode 100644 .venv/Lib/site-packages/pydantic/decorator.py create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__init__.py create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__pycache__/class_validators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__pycache__/config.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__pycache__/copy_internals.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__pycache__/decorator.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__pycache__/json.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__pycache__/parse.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/__pycache__/tools.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/class_validators.py create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/config.py create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/copy_internals.py create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/decorator.py create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/json.py create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/parse.py create mode 100644 .venv/Lib/site-packages/pydantic/deprecated/tools.py create mode 100644 .venv/Lib/site-packages/pydantic/env_settings.py create mode 100644 .venv/Lib/site-packages/pydantic/error_wrappers.py create mode 100644 .venv/Lib/site-packages/pydantic/errors.py create mode 100644 .venv/Lib/site-packages/pydantic/experimental/__init__.py create mode 100644 .venv/Lib/site-packages/pydantic/experimental/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/experimental/__pycache__/arguments_schema.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/experimental/__pycache__/pipeline.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/experimental/arguments_schema.py create mode 100644 .venv/Lib/site-packages/pydantic/experimental/pipeline.py create mode 100644 .venv/Lib/site-packages/pydantic/fields.py create mode 100644 .venv/Lib/site-packages/pydantic/functional_serializers.py create mode 100644 .venv/Lib/site-packages/pydantic/functional_validators.py create mode 100644 .venv/Lib/site-packages/pydantic/generics.py create mode 100644 .venv/Lib/site-packages/pydantic/json.py create mode 100644 .venv/Lib/site-packages/pydantic/json_schema.py create mode 100644 .venv/Lib/site-packages/pydantic/main.py create mode 100644 .venv/Lib/site-packages/pydantic/mypy.py create mode 100644 .venv/Lib/site-packages/pydantic/networks.py create mode 100644 .venv/Lib/site-packages/pydantic/parse.py create mode 100644 .venv/Lib/site-packages/pydantic/plugin/__init__.py create mode 100644 .venv/Lib/site-packages/pydantic/plugin/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/plugin/__pycache__/_loader.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/plugin/__pycache__/_schema_validator.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/plugin/_loader.py create mode 100644 .venv/Lib/site-packages/pydantic/plugin/_schema_validator.py create mode 100644 .venv/Lib/site-packages/pydantic/py.typed create mode 100644 .venv/Lib/site-packages/pydantic/root_model.py create mode 100644 .venv/Lib/site-packages/pydantic/schema.py create mode 100644 .venv/Lib/site-packages/pydantic/tools.py create mode 100644 .venv/Lib/site-packages/pydantic/type_adapter.py create mode 100644 .venv/Lib/site-packages/pydantic/types.py create mode 100644 .venv/Lib/site-packages/pydantic/typing.py create mode 100644 .venv/Lib/site-packages/pydantic/utils.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/__init__.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/_hypothesis_plugin.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/annotated_types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/class_validators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/color.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/config.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/dataclasses.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/datetime_parse.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/decorator.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/env_settings.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/error_wrappers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/errors.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/fields.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/generics.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/json.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/main.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/mypy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/networks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/parse.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/schema.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/tools.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/typing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/validators.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/__pycache__/version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic/v1/_hypothesis_plugin.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/annotated_types.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/class_validators.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/color.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/config.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/dataclasses.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/datetime_parse.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/decorator.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/env_settings.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/error_wrappers.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/errors.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/fields.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/generics.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/json.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/main.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/mypy.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/networks.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/parse.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/py.typed create mode 100644 .venv/Lib/site-packages/pydantic/v1/schema.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/tools.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/types.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/typing.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/utils.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/validators.py create mode 100644 .venv/Lib/site-packages/pydantic/v1/version.py create mode 100644 .venv/Lib/site-packages/pydantic/validate_call_decorator.py create mode 100644 .venv/Lib/site-packages/pydantic/validators.py create mode 100644 .venv/Lib/site-packages/pydantic/version.py create mode 100644 .venv/Lib/site-packages/pydantic/warnings.py create mode 100644 .venv/Lib/site-packages/pydantic_core-2.33.2.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/pydantic_core-2.33.2.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/pydantic_core-2.33.2.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/pydantic_core-2.33.2.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/pydantic_core-2.33.2.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/pydantic_core/__init__.py create mode 100644 .venv/Lib/site-packages/pydantic_core/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic_core/__pycache__/core_schema.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pydantic_core/_pydantic_core.cp312-win_amd64.pyd create mode 100644 .venv/Lib/site-packages/pydantic_core/_pydantic_core.pyi create mode 100644 .venv/Lib/site-packages/pydantic_core/core_schema.py create mode 100644 .venv/Lib/site-packages/pydantic_core/py.typed create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/AUTHORS create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/LICENSE create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/REQUESTED create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/pytest-8.3.5.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/pytest/__init__.py create mode 100644 .venv/Lib/site-packages/pytest/__main__.py create mode 100644 .venv/Lib/site-packages/pytest/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pytest/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/pytest/py.typed create mode 100644 .venv/Lib/site-packages/requests-2.32.3.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/requests-2.32.3.dist-info/LICENSE create mode 100644 .venv/Lib/site-packages/requests-2.32.3.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/requests-2.32.3.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/requests-2.32.3.dist-info/REQUESTED create mode 100644 .venv/Lib/site-packages/requests-2.32.3.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/requests-2.32.3.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/requests/__init__.py create mode 100644 .venv/Lib/site-packages/requests/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/__version__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/_internal_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/adapters.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/api.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/auth.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/certs.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/compat.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/cookies.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/help.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/hooks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/models.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/packages.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/sessions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/status_codes.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/structures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/requests/__version__.py create mode 100644 .venv/Lib/site-packages/requests/_internal_utils.py create mode 100644 .venv/Lib/site-packages/requests/adapters.py create mode 100644 .venv/Lib/site-packages/requests/api.py create mode 100644 .venv/Lib/site-packages/requests/auth.py create mode 100644 .venv/Lib/site-packages/requests/certs.py create mode 100644 .venv/Lib/site-packages/requests/compat.py create mode 100644 .venv/Lib/site-packages/requests/cookies.py create mode 100644 .venv/Lib/site-packages/requests/exceptions.py create mode 100644 .venv/Lib/site-packages/requests/help.py create mode 100644 .venv/Lib/site-packages/requests/hooks.py create mode 100644 .venv/Lib/site-packages/requests/models.py create mode 100644 .venv/Lib/site-packages/requests/packages.py create mode 100644 .venv/Lib/site-packages/requests/sessions.py create mode 100644 .venv/Lib/site-packages/requests/status_codes.py create mode 100644 .venv/Lib/site-packages/requests/structures.py create mode 100644 .venv/Lib/site-packages/requests/utils.py create mode 100644 .venv/Lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/sniffio-1.3.1.dist-info/LICENSE create mode 100644 .venv/Lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 create mode 100644 .venv/Lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT create mode 100644 .venv/Lib/site-packages/sniffio-1.3.1.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/sniffio-1.3.1.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/sniffio-1.3.1.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt create mode 100644 .venv/Lib/site-packages/sniffio/__init__.py create mode 100644 .venv/Lib/site-packages/sniffio/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/sniffio/__pycache__/_impl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/sniffio/__pycache__/_version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/sniffio/_impl.py create mode 100644 .venv/Lib/site-packages/sniffio/_tests/__init__.py create mode 100644 .venv/Lib/site-packages/sniffio/_tests/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/sniffio/_tests/__pycache__/test_sniffio.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/sniffio/_tests/test_sniffio.py create mode 100644 .venv/Lib/site-packages/sniffio/_version.py create mode 100644 .venv/Lib/site-packages/sniffio/py.typed create mode 100644 .venv/Lib/site-packages/starlette-0.46.2.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/starlette-0.46.2.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/starlette-0.46.2.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/starlette-0.46.2.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/starlette-0.46.2.dist-info/licenses/LICENSE.md create mode 100644 .venv/Lib/site-packages/starlette/__init__.py create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/_exception_handler.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/_utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/applications.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/authentication.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/background.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/concurrency.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/config.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/convertors.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/datastructures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/endpoints.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/formparsers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/requests.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/responses.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/routing.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/schemas.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/staticfiles.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/status.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/templating.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/testclient.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/__pycache__/websockets.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/_exception_handler.py create mode 100644 .venv/Lib/site-packages/starlette/_utils.py create mode 100644 .venv/Lib/site-packages/starlette/applications.py create mode 100644 .venv/Lib/site-packages/starlette/authentication.py create mode 100644 .venv/Lib/site-packages/starlette/background.py create mode 100644 .venv/Lib/site-packages/starlette/concurrency.py create mode 100644 .venv/Lib/site-packages/starlette/config.py create mode 100644 .venv/Lib/site-packages/starlette/convertors.py create mode 100644 .venv/Lib/site-packages/starlette/datastructures.py create mode 100644 .venv/Lib/site-packages/starlette/endpoints.py create mode 100644 .venv/Lib/site-packages/starlette/exceptions.py create mode 100644 .venv/Lib/site-packages/starlette/formparsers.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/__init__.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/authentication.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/base.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/cors.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/errors.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/gzip.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/httpsredirect.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/sessions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/trustedhost.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/__pycache__/wsgi.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/starlette/middleware/authentication.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/base.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/cors.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/errors.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/exceptions.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/gzip.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/httpsredirect.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/sessions.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/trustedhost.py create mode 100644 .venv/Lib/site-packages/starlette/middleware/wsgi.py create mode 100644 .venv/Lib/site-packages/starlette/py.typed create mode 100644 .venv/Lib/site-packages/starlette/requests.py create mode 100644 .venv/Lib/site-packages/starlette/responses.py create mode 100644 .venv/Lib/site-packages/starlette/routing.py create mode 100644 .venv/Lib/site-packages/starlette/schemas.py create mode 100644 .venv/Lib/site-packages/starlette/staticfiles.py create mode 100644 .venv/Lib/site-packages/starlette/status.py create mode 100644 .venv/Lib/site-packages/starlette/templating.py create mode 100644 .venv/Lib/site-packages/starlette/testclient.py create mode 100644 .venv/Lib/site-packages/starlette/types.py create mode 100644 .venv/Lib/site-packages/starlette/websockets.py create mode 100644 .venv/Lib/site-packages/typing_extensions-4.13.2.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/typing_extensions-4.13.2.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/typing_extensions-4.13.2.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/typing_extensions-4.13.2.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/typing_extensions-4.13.2.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/typing_extensions.py create mode 100644 .venv/Lib/site-packages/typing_inspection-0.4.1.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/typing_inspection-0.4.1.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/typing_inspection-0.4.1.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/typing_inspection-0.4.1.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/typing_inspection-0.4.1.dist-info/licenses/LICENSE create mode 100644 .venv/Lib/site-packages/typing_inspection/__init__.py create mode 100644 .venv/Lib/site-packages/typing_inspection/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/typing_inspection/__pycache__/introspection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/typing_inspection/__pycache__/typing_objects.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/typing_inspection/introspection.py create mode 100644 .venv/Lib/site-packages/typing_inspection/py.typed create mode 100644 .venv/Lib/site-packages/typing_inspection/typing_objects.py create mode 100644 .venv/Lib/site-packages/typing_inspection/typing_objects.pyi create mode 100644 .venv/Lib/site-packages/urllib3-2.4.0.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/urllib3-2.4.0.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/urllib3-2.4.0.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/urllib3-2.4.0.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/urllib3-2.4.0.dist-info/licenses/LICENSE.txt create mode 100644 .venv/Lib/site-packages/urllib3/__init__.py create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/_collections.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/_version.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/connectionpool.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/fields.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/filepost.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/__pycache__/response.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/_base_connection.py create mode 100644 .venv/Lib/site-packages/urllib3/_collections.py create mode 100644 .venv/Lib/site-packages/urllib3/_request_methods.py create mode 100644 .venv/Lib/site-packages/urllib3/_version.py create mode 100644 .venv/Lib/site-packages/urllib3/connection.py create mode 100644 .venv/Lib/site-packages/urllib3/connectionpool.py create mode 100644 .venv/Lib/site-packages/urllib3/contrib/__init__.py create mode 100644 .venv/Lib/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/__init__.py create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/connection.py create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/fetch.py create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/request.py create mode 100644 .venv/Lib/site-packages/urllib3/contrib/emscripten/response.py create mode 100644 .venv/Lib/site-packages/urllib3/contrib/pyopenssl.py create mode 100644 .venv/Lib/site-packages/urllib3/contrib/socks.py create mode 100644 .venv/Lib/site-packages/urllib3/exceptions.py create mode 100644 .venv/Lib/site-packages/urllib3/fields.py create mode 100644 .venv/Lib/site-packages/urllib3/filepost.py create mode 100644 .venv/Lib/site-packages/urllib3/http2/__init__.py create mode 100644 .venv/Lib/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/http2/__pycache__/connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/http2/connection.py create mode 100644 .venv/Lib/site-packages/urllib3/http2/probe.py create mode 100644 .venv/Lib/site-packages/urllib3/poolmanager.py create mode 100644 .venv/Lib/site-packages/urllib3/py.typed create mode 100644 .venv/Lib/site-packages/urllib3/response.py create mode 100644 .venv/Lib/site-packages/urllib3/util/__init__.py create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/request.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/ssl_.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/timeout.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/util.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/urllib3/util/connection.py create mode 100644 .venv/Lib/site-packages/urllib3/util/proxy.py create mode 100644 .venv/Lib/site-packages/urllib3/util/request.py create mode 100644 .venv/Lib/site-packages/urllib3/util/response.py create mode 100644 .venv/Lib/site-packages/urllib3/util/retry.py create mode 100644 .venv/Lib/site-packages/urllib3/util/ssl_.py create mode 100644 .venv/Lib/site-packages/urllib3/util/ssl_match_hostname.py create mode 100644 .venv/Lib/site-packages/urllib3/util/ssltransport.py create mode 100644 .venv/Lib/site-packages/urllib3/util/timeout.py create mode 100644 .venv/Lib/site-packages/urllib3/util/url.py create mode 100644 .venv/Lib/site-packages/urllib3/util/util.py create mode 100644 .venv/Lib/site-packages/urllib3/util/wait.py create mode 100644 .venv/Lib/site-packages/uvicorn-0.34.2.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/uvicorn-0.34.2.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/uvicorn-0.34.2.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/uvicorn-0.34.2.dist-info/REQUESTED create mode 100644 .venv/Lib/site-packages/uvicorn-0.34.2.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/uvicorn-0.34.2.dist-info/entry_points.txt create mode 100644 .venv/Lib/site-packages/uvicorn-0.34.2.dist-info/licenses/LICENSE.md create mode 100644 .venv/Lib/site-packages/uvicorn/__init__.py create mode 100644 .venv/Lib/site-packages/uvicorn/__main__.py create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/__main__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/_subprocess.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/_types.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/config.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/importer.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/logging.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/main.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/server.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/__pycache__/workers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/_subprocess.py create mode 100644 .venv/Lib/site-packages/uvicorn/_types.py create mode 100644 .venv/Lib/site-packages/uvicorn/config.py create mode 100644 .venv/Lib/site-packages/uvicorn/importer.py create mode 100644 .venv/Lib/site-packages/uvicorn/lifespan/__init__.py create mode 100644 .venv/Lib/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/lifespan/__pycache__/off.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/lifespan/__pycache__/on.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/lifespan/off.py create mode 100644 .venv/Lib/site-packages/uvicorn/lifespan/on.py create mode 100644 .venv/Lib/site-packages/uvicorn/logging.py create mode 100644 .venv/Lib/site-packages/uvicorn/loops/__init__.py create mode 100644 .venv/Lib/site-packages/uvicorn/loops/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/loops/__pycache__/asyncio.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/loops/__pycache__/auto.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/loops/asyncio.py create mode 100644 .venv/Lib/site-packages/uvicorn/loops/auto.py create mode 100644 .venv/Lib/site-packages/uvicorn/loops/uvloop.py create mode 100644 .venv/Lib/site-packages/uvicorn/main.py create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/__init__.py create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/asgi2.py create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/message_logger.py create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/proxy_headers.py create mode 100644 .venv/Lib/site-packages/uvicorn/middleware/wsgi.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/__init__.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/__init__.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/__pycache__/h11_impl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/auto.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/flow_control.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/h11_impl.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/http/httptools_impl.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/utils.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/websockets/__init__.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/websockets/__pycache__/wsproto_impl.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/websockets/auto.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/websockets/websockets_impl.py create mode 100644 .venv/Lib/site-packages/uvicorn/protocols/websockets/wsproto_impl.py create mode 100644 .venv/Lib/site-packages/uvicorn/py.typed create mode 100644 .venv/Lib/site-packages/uvicorn/server.py create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/__init__.py create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/__pycache__/statreload.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/basereload.py create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/multiprocess.py create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/statreload.py create mode 100644 .venv/Lib/site-packages/uvicorn/supervisors/watchfilesreload.py create mode 100644 .venv/Lib/site-packages/uvicorn/workers.py create mode 100644 .venv/Lib/site-packages/werkzeug-3.1.3.dist-info/INSTALLER create mode 100644 .venv/Lib/site-packages/werkzeug-3.1.3.dist-info/LICENSE.txt create mode 100644 .venv/Lib/site-packages/werkzeug-3.1.3.dist-info/METADATA create mode 100644 .venv/Lib/site-packages/werkzeug-3.1.3.dist-info/RECORD create mode 100644 .venv/Lib/site-packages/werkzeug-3.1.3.dist-info/WHEEL create mode 100644 .venv/Lib/site-packages/werkzeug/__init__.py create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/_internal.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/_reloader.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/http.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/local.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/security.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/serving.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/test.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/urls.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/__pycache__/wsgi.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/_internal.py create mode 100644 .venv/Lib/site-packages/werkzeug/_reloader.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__init__.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/accept.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/cache_control.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/headers.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/range.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/__pycache__/structures.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/accept.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/auth.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/cache_control.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/csp.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/etag.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/file_storage.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/headers.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/mixins.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/range.py create mode 100644 .venv/Lib/site-packages/werkzeug/datastructures/structures.py create mode 100644 .venv/Lib/site-packages/werkzeug/debug/__init__.py create mode 100644 .venv/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/debug/__pycache__/repr.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/debug/console.py create mode 100644 .venv/Lib/site-packages/werkzeug/debug/repr.py create mode 100644 .venv/Lib/site-packages/werkzeug/debug/shared/ICON_LICENSE.md create mode 100644 .venv/Lib/site-packages/werkzeug/debug/shared/console.png create mode 100644 .venv/Lib/site-packages/werkzeug/debug/shared/debugger.js create mode 100644 .venv/Lib/site-packages/werkzeug/debug/shared/less.png create mode 100644 .venv/Lib/site-packages/werkzeug/debug/shared/more.png create mode 100644 .venv/Lib/site-packages/werkzeug/debug/shared/style.css create mode 100644 .venv/Lib/site-packages/werkzeug/debug/tbtools.py create mode 100644 .venv/Lib/site-packages/werkzeug/exceptions.py create mode 100644 .venv/Lib/site-packages/werkzeug/formparser.py create mode 100644 .venv/Lib/site-packages/werkzeug/http.py create mode 100644 .venv/Lib/site-packages/werkzeug/local.py create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/__init__.py create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/dispatcher.py create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/http_proxy.py create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/lint.py create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/profiler.py create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/proxy_fix.py create mode 100644 .venv/Lib/site-packages/werkzeug/middleware/shared_data.py create mode 100644 .venv/Lib/site-packages/werkzeug/py.typed create mode 100644 .venv/Lib/site-packages/werkzeug/routing/__init__.py create mode 100644 .venv/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/routing/converters.py create mode 100644 .venv/Lib/site-packages/werkzeug/routing/exceptions.py create mode 100644 .venv/Lib/site-packages/werkzeug/routing/map.py create mode 100644 .venv/Lib/site-packages/werkzeug/routing/matcher.py create mode 100644 .venv/Lib/site-packages/werkzeug/routing/rules.py create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/__init__.py create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/http.py create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/multipart.py create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/request.py create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/response.py create mode 100644 .venv/Lib/site-packages/werkzeug/sansio/utils.py create mode 100644 .venv/Lib/site-packages/werkzeug/security.py create mode 100644 .venv/Lib/site-packages/werkzeug/serving.py create mode 100644 .venv/Lib/site-packages/werkzeug/test.py create mode 100644 .venv/Lib/site-packages/werkzeug/testapp.py create mode 100644 .venv/Lib/site-packages/werkzeug/urls.py create mode 100644 .venv/Lib/site-packages/werkzeug/user_agent.py create mode 100644 .venv/Lib/site-packages/werkzeug/utils.py create mode 100644 .venv/Lib/site-packages/werkzeug/wrappers/__init__.py create mode 100644 .venv/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-312.pyc create mode 100644 .venv/Lib/site-packages/werkzeug/wrappers/request.py create mode 100644 .venv/Lib/site-packages/werkzeug/wrappers/response.py create mode 100644 .venv/Lib/site-packages/werkzeug/wsgi.py create mode 100644 .venv/Scripts/Activate.ps1 create mode 100644 .venv/Scripts/activate create mode 100644 .venv/Scripts/activate.bat create mode 100644 .venv/Scripts/deactivate.bat create mode 100644 .venv/Scripts/fastapi.exe create mode 100644 .venv/Scripts/flask.exe create mode 100644 .venv/Scripts/httpx.exe create mode 100644 .venv/Scripts/normalizer.exe create mode 100644 .venv/Scripts/pip.exe create mode 100644 .venv/Scripts/pip3.12.exe create mode 100644 .venv/Scripts/pip3.exe create mode 100644 .venv/Scripts/py.test.exe create mode 100644 .venv/Scripts/pytest.exe create mode 100644 .venv/Scripts/python.exe create mode 100644 .venv/Scripts/pythonw.exe create mode 100644 .venv/Scripts/uvicorn.exe create mode 100644 .venv/pyvenv.cfg create mode 100644 Design Docs/File_Tree.txt create mode 100644 Design Docs/MakeTree_Put this at root.bat create mode 100644 Design Docs/TDD.md create mode 100644 SOP/Diagnostic.md create mode 100644 SOP/Handoff.md create mode 100644 custom.css create mode 100644 main.log create mode 100644 main.py create mode 100644 project/tests/__pycache__/test_flask_route.cpython-312-pytest-8.3.5.pyc create mode 100644 project/tests/test_flask_route.py create mode 100644 requirements.txt diff --git a/.cursor/rules/Agent_Requested.mdc b/.cursor/rules/Agent_Requested.mdc new file mode 100644 index 0000000..85d81dc --- /dev/null +++ b/.cursor/rules/Agent_Requested.mdc @@ -0,0 +1,40 @@ +--- +description: Agent Requested +--- + +## Minimalist Code Refactoring Guidelines for Cursor AI## Core Principles + +### 1. Markup Minimization +- Eliminate unnecessary nested elements +- Remove redundant wrapper divs +- Modify existing elements before creating new ones +- Repurpose underutilized elements rather than adding new containers + +### 2. CSS Consolidation +- Combine related styles into existing selectors +- Extend current class functionality instead of creating new classes +- Use inheritance and cascading to reduce rule repetition +- Modify existing classes to accommodate new requirements + +### 3. DRY Implementation +- Identify and consolidate identical styling patterns +- Reuse existing components before creating new ones +- Extend functionality of existing components when possible +- Leverage CSS variables to maintain consistency + +### 4. Structural Optimization +- Flatten nested structures without changing functionality +- Assess if existing parent elements can handle child element responsibilities +- Combine adjacent elements serving similar purposes +- Question every layer of nesting to determine if it's truly necessary + +## Implementation Strategy + +Always prioritize: +1. Modifying existing elements over creating new ones +2. Extending current classes before introducing new ones +3. Maintaining the absolute minimum number of containers, wrappers, and functions +4. Challenging each element's necessity before preserving it +5. Using semantic HTML elements with proper attributes instead of generic containers + +Remember: The most efficient code is often the code you don't write. Seek to reduce, reuse, and repurpose before adding anything new. \ No newline at end of file diff --git a/.cursor/rules/Always.mdc b/.cursor/rules/Always.mdc new file mode 100644 index 0000000..ee791dd --- /dev/null +++ b/.cursor/rules/Always.mdc @@ -0,0 +1,74 @@ +--- +description: +globs: +alwaysApply: true +--- + +this part has priority over any other instruction + +--- +**HIGH PRIORITY INSTRUCTION:** + +**For tasks requiring user input or information gathering:** +1. **Assign only ONE step/task at a time.** +2. **Wait for the user's response before proceeding.** +3. **If the user's response is satisfactory, move to the next step.** +4. **If the user's response is inaccurate, explain where and how the correct information can be collected. Do NOT proceed to the next step until accurate information is provided.** +5. **NEVER assign more than ONE tasks at once to avoid confusion and inaccurate responses.** + + +--- +description: Keep chat responses concise and actionable; minimize verbosity in the right panel. +alwaysApply: true +--- + +- Only display the most important information and actionable steps in the right panel chat window. +- Avoid lengthy explanations or verbose responses; focus on what the user needs to do next (e.g., run a command, install a package, check a log). +- It is acceptable to take extra time to think or process internally, but do not display all your reasoning or intermediate steps unless explicitly requested. +- Prioritize brevity and clarity. If a response can be summarized, do so. +- Assume the user prefers minimal reading and does not want to sift through long text. +- Only elaborate if the user asks for more detail. + + + + +1. "Always focus exclusively on the task I've explicitly requested. If you notice other issues in the code, do not attempt to fix them unless I specifically ask." + +2. "When making changes, make the minimal edit necessary to accomplish exactly what I've asked for. Do not extend the scope of changes beyond what was requested." + +3. "If you notice potential issues unrelated to my request, briefly note them at the end of your response only after completing the requested task, but do not attempt to fix them." + +4. "Prioritize completing the specific task over perfecting the entire codebase. One successful focused change is better than trying to fix everything at once." + +5. "When in doubt about scope, ask me to clarify rather than making assumptions about what should be changed." + +6.ensure you stay laser-focused on the specific request without getting distracted by other issues you may notice in the code. + + +rules: + - name: file-size-limit + pattern: "**/*.{js,ts,jsx,tsx,py,go,rs,java}" + when: + lines: ">1000" + action: | + **Automated File Splitting Protocol** + 1. Analyze this file's structure and functionality + 2. Identify logical splitting points (component boundaries, related functions, etc) + 3. Create new adjacent file with _partX suffix (e.g., `utils_part2.ts`) + 4. Move 30-40% of code to new file while maintaining functionality + 5. Add cross-references between original and new files + 6. Verify imports/exports work across split files + + If splitting fails after 3 attempts: + [FALLBACK] → "This file exceeds 1000 lines. Please manually split it into smaller modules." + + - name: edit-fallback + pattern: "**/*" + action: | + **Change Implementation Protocol** + 1. Attempt automatic implementation (3 retries) + 2. If failure persists: + a. Display exact code changes needed + b. Show line numbers/context + c. Provide step-by-step manual implementation guide + d. Offer to create TODO comments at affected positions diff --git a/.cursor/rules/Auto_Attached_Blank.mdc b/.cursor/rules/Auto_Attached_Blank.mdc new file mode 100644 index 0000000..a345d21 --- /dev/null +++ b/.cursor/rules/Auto_Attached_Blank.mdc @@ -0,0 +1,4 @@ +--- +description: Auto Attached +--- + diff --git a/.cursor/rules/Manual.mdc b/.cursor/rules/Manual.mdc new file mode 100644 index 0000000..770ae25 --- /dev/null +++ b/.cursor/rules/Manual.mdc @@ -0,0 +1,28 @@ +--- +description: +globs: +alwaysApply: false +--- +IF I ASK FOR FIX OR EXPLANATION, I WANT ACTUAL CODE OR EXPLANATION!!! I DON'T WANT "Here's how you can blablabla" + +- Be casual unless otherwise specified +- Be terse +- Suggest solutions that I didn't think about—anticipate my needs +- Treat me as an expert +- Be accurate and thorough +- Give the answer immediately. Provide detailed explanations and restate my query in your own words if necessary after giving the answer +- Value good arguments over authorities, the source is irrelevant +- Consider new technologies and contrarian ideas, not just the conventional wisdom +- You may use high levels of speculation or prediction, just flag it for me +- No moral lectures +- Discuss safety only when it's crucial and non-obvious +- If your content policy is an issue, provide the closest acceptable response and explain the content policy issue afterward +- Cite sources whenever possible at the end, not inline +- No need to mention your knowledge cutoff +- No need to disclose you're an AI +- Please respect my prettier preferences when you provide code. +- Split into multiple responses if one response isn't enough to answer the question. + +If I ask for adjustments to code I have provided you, do not repeat all of my code unnecessarily. Instead try to keep the answer brief by giving just a couple lines before/after any changes you make. Multiple code blocks are ok. + +Source: https://github.com/piotr-jura-udemy/cursorai-nextjs/blob/main/rules-for-ai.txt \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2010 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/METADATA b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/METADATA new file mode 100644 index 0000000..82261f2 --- /dev/null +++ b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/METADATA @@ -0,0 +1,92 @@ +Metadata-Version: 2.1 +Name: MarkupSafe +Version: 3.0.2 +Summary: Safely add untrusted strings to HTML/XML markup. +Maintainer-email: Pallets +License: 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. + +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://markupsafe.palletsprojects.com/ +Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/ +Project-URL: Source, https://github.com/pallets/markupsafe/ +Project-URL: Chat, https://discord.gg/pallets +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Text Processing :: Markup :: HTML +Classifier: Typing :: Typed +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-File: LICENSE.txt + +# MarkupSafe + +MarkupSafe implements a text object that escapes characters so it is +safe to use in HTML and XML. Characters that have special meanings are +replaced so that they display as the actual characters. This mitigates +injection attacks, meaning untrusted user input can safely be displayed +on a page. + + +## Examples + +```pycon +>>> from markupsafe import Markup, escape + +>>> # escape replaces special characters and wraps in Markup +>>> escape("") +Markup('<script>alert(document.cookie);</script>') + +>>> # wrap in Markup to mark text "safe" and prevent escaping +>>> Markup("Hello") +Markup('hello') + +>>> escape(Markup("Hello")) +Markup('hello') + +>>> # Markup is a str subclass +>>> # methods and operators escape their arguments +>>> template = Markup("Hello {name}") +>>> template.format(name='"World"') +Markup('Hello "World"') +``` + +## Donate + +The Pallets organization develops and supports MarkupSafe and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +[please donate today][]. + +[please donate today]: https://palletsprojects.com/donate diff --git a/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/RECORD b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/RECORD new file mode 100644 index 0000000..26bf81c --- /dev/null +++ b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/RECORD @@ -0,0 +1,14 @@ +MarkupSafe-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +MarkupSafe-3.0.2.dist-info/LICENSE.txt,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503 +MarkupSafe-3.0.2.dist-info/METADATA,sha256=nhoabjupBG41j_JxPCJ3ylgrZ6Fx8oMCFbiLF9Kafqc,4067 +MarkupSafe-3.0.2.dist-info/RECORD,, +MarkupSafe-3.0.2.dist-info/WHEEL,sha256=62QJgqtUFevqILau0n0UncooEMoOyVCKVQitJpcuCig,101 +MarkupSafe-3.0.2.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 +markupsafe/__init__.py,sha256=pREerPwvinB62tNCMOwqxBS2YHV6R52Wcq1d-rB4Z5o,13609 +markupsafe/__pycache__/__init__.cpython-312.pyc,, +markupsafe/__pycache__/_native.cpython-312.pyc,, +markupsafe/_native.py,sha256=2ptkJ40yCcp9kq3L1NqpgjfpZB-obniYKFFKUOkHh4Q,218 +markupsafe/_speedups.c,sha256=SglUjn40ti9YgQAO--OgkSyv9tXq9vvaHyVhQows4Ok,4353 +markupsafe/_speedups.cp312-win_amd64.pyd,sha256=sC88mCi7HJOQhbSSrdMPZfdCvi_VBfOzwkVuQ7V6T3M,13312 +markupsafe/_speedups.pyi,sha256=LSDmXYOefH4HVpAXuL8sl7AttLw0oXh1njVoVZp2wqQ,42 +markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL new file mode 100644 index 0000000..bd4c259 --- /dev/null +++ b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.2.0) +Root-Is-Purelib: false +Tag: cp312-cp312-win_amd64 + diff --git a/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt new file mode 100644 index 0000000..75bf729 --- /dev/null +++ b/.venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt @@ -0,0 +1 @@ +markupsafe diff --git a/.venv/Lib/site-packages/__pycache__/py.cpython-312.pyc b/.venv/Lib/site-packages/__pycache__/py.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33c9561c9c59bc6c2490a224770a0992f82bbee4 GIT binary patch literal 515 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±=I_P7f7pSTT=DN!xb*+j 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 S+nwIooFLe%?`pvswE72$k+_`z literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/cacheprovider.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/cacheprovider.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b89df4ba202d16eaa7b1e1cebe52811fd6d8b372 GIT binary patch literal 30885 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

D(h}1-etnMb zO3KWm?823}XEDDcm`47te zvgX5@|4{dXCOm#A(FXoTMI*mxZRC?(OqjyF)KA68!8OT~RRIe~HpQdA?5a+HHK5{0 zUfPi~`2ERBndZbk3UkCbIc{acm8_6|P>o5O;Mo<<Y7L&{-?O2uS!U3Fhte2NKnhQI(ZVlz{VB!FBFUJhZZo#pdv~%mSX&VfL zs9eU;ly)>_9P84Kb)<2ym7KHATT>oC{TP?qL$yCS|76CoHtkq@xhL&tJ=>SHI?m3J z%Y4%>oaNuy2LXr6cW!)sJmYFfyIRf;f<*n^kxWH*8vh;L$d8^3e0yMSQ>q;2055Nc z!<1!H*KKc2x~Bi~;j6<}hHsdbTemFL^#855f6jW_RkqNcDQ``e!(MNxykp7L32*&s z6Afus!=iM#f628TsSejd>-?@PThfQOjG>7~!;O5?wyi+%Q6 zuYddXH)qjv91vT7asB1POSS8lydC%zD-4@u-ZCepDq$jd4Sm?MY}@+tvYNSGP*I+; zbJO$Fzxz5)VJblr<&AL>(0UrAnjA{=yEw-i_H{W>SFOL4`=%2lu+0vih(*IF7 zwRvBvdjBoYGZ68VlRrJD?4|%B&}PiPSO#b)FlAb8fR(JkLV<}@zw%R*>cMqIJJd~8 z__Qi$b)aHKm=Y~5SNtWV7Mff!peNFUz0VoZL}05(MpNYPZFGu$KN0TQ2+xV%p>?f&+X<|o|n1ccQ)}Ab9kzKAh zRH;iS8W$NVaH%OW8!vn1qh-a6w{kVbuY-^xllYH1K;sFgGKv^S0FH>@r#4^$Tuv`M z>I+5sE{R^Ml#O73#*#)QSE}Y>25GXh8lZPB=T={+A5%@GrzxNfK4;fl;mL^VJiQ_8 z&#b(dB26SYBa;&`!Gw(dh#!!VT(L1)&bCYDL!T0dkp=%b$%+s}JJ0_;*YPVG$2b22 zSN?O(`E$D;9gX>8xv|(p+syaX!d24c090%ZcR2)Kw8X$h@OL?O`{KBdy^E{%LZ3XQ)|WQLGn({d0qp_fw*Mg~b|5dCz@+J1;M!{~tVIrO literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/capture.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/capture.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ddf5c3db7b00c31b2b4b826593fbe77bbe610cd GIT binary patch literal 54040 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/compat.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/compat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a239c9058458bb9c3d5aec9becf0040947aea63b GIT binary patch literal 12548 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 V9!$@%=}%oX=bm=|v@U@3{{iUH?Zf~8 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/debugging.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/debugging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a60d5381e10b6529099ee005143d4dd1d7d006c8 GIT binary patch literal 17940 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 ^l6$;P&tbAH#!sn6mFtb_JPU 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/deprecated.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/deprecated.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62680574fa962d4f72c56e4bff3d1446c48c95d0 GIT binary patch literal 2585 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 I#&)v*AIPg^+yDRo literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/faulthandler.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/faulthandler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffbb724c8c99ade5223c6f15566f13b796e4b1c4 GIT binary patch literal 4451 zcmbtXU2GFa9-rA=+ndC8VkaS=BumHy>t=V-T zkn{qo1oQ=Xxq$AZg6;tjoUVN;uRPyN+!!TVf`qP|?ke3w4)+07C*A#Lznp-fNH@~X z%+CD(GxMMS@BcA>4Ft*&lsk$2W6>Z&Khhtka8?HwKEVjhAqh!XK`9)?nD!Zki85x( zMp&cX?X7ztqTbuXblVp>G$|_0{a77ryn=z#}xG`DDXjE&0@QZq2f9y2eg%&$0 zreX(WZ9-Qwv6D$j7ETa#Tu$g(?69cm{U?qJlbUc?5w(ffM<9nxgoEn18ta;r)044* zhjhMOo^ki7K>-5s8rilAGIqpWmH+?aX6Nmp^73QI7t{jZ39%ZXobUNT`Mf| zl&$c{Q*mkV!r**(^WFA`?CjuUuDbw4XpNSrb(vav2_32>2`Oj>>$Wb6Rxkrv8mBsa zr8t-+)zb{<*Zaye{OY_j)=mE^oWF6u(<`>qjDzkstOee|Ds(M45taXy@@t&bwja3r zP_*4+hX_?E#q>y0kt1b>fA!THp;1|nYq}&85~l=d)hJJDaXBpxE3#yGG-?1wyU7!G zprw-JXfiD-E~6Bmkf#hr)eKh3W{jq_-;bP{*>9snvhM#hFJZvnz5B{d2{ zvK}@hbVbqxmElP!DtIQUebNF|gdwf8Pf=vMFAFtc+ofdFMcn*`GEl-vs7%~EqpGjZ zarJqw@wu<<*2o>@wvzMhSmw*G@>lq4>*kuTH|JaS zeed0!{LA#c>74I_CB9~nZ_4paHx7R@aC0CZ8O${g<@t|YhMt_icg2JFZVOPby<1#Q z^)DEjLuE*Jr^f9l>JC8XZN6#-R3qVu&2S&(y3gTqKzI@2)Je1v>8>v;dax0u9g&>? zQ-=wag5WH33|+);r+M4?63l0Cf-~qW`x!ceLFGUF7fQryu-cSncr71bcx_Jzs|OC9fB4!bUw1Eug@th2yuWR!vi5S|<+8u=kH_XKI$j`;r{iZHRaIa8 z^2(Rdqxyh)D157|nQVTB{`Y0Xg0qUDMzfd2aH>*rf{FNF5|5bAljaiOAjp6fO7 zdkQx5ogrc-BNM}?lPO35^fgjaP;aD35*!&Fht%g4NlO6RkrUD>3?M!Tave3pHUl9V zl{5tx$grDTijby0V}_k(#@)c|(9i%o%|fWOPl3VcB=mb`4g+2EVo7-oKYh|P8fId!MGyoaJ5YdF`bZ=c&W(R9#J|3fTu_$Klh`Q2i6VoJG%rn{MfM z&fh-25ZpO?WXTu27P@%;3E%WAR6Eyny~(nh{Kno~sPD4Zbb65-AGzD{&-}ASs+RMS0;QRs&?%01c>bo!YSN8YeM`5;q3wl&rOZ!{G z{oU-NZWj790S9P}Bne;(sNF)f29|;gozO8OFaxO_jY=i#r>Hx460qk!JYe3aAf~5Q zW#9CElj%`G%0iZ{3igEx!}18tyJfgGLZ>s*aF>}+4c%j`+uj2wxQDIaQ}jP>#$s~D zfsIgO{~SK88md`T@FGvW8G9iA=zPp|y&e|+A1R04MM`)~B&lR#LQhVDlxGvNnR=oFg+N?yt)n;RsEA2{mQIOJqGu z(gruAre&BhBRh4Nq@Py5g$m+5?Hep?^E$;gO2HB8_6d!DChfUa_rdo$UAR+Fhv^%} zX}_@7wAODj%H#2oESy7*#|cH0nRpNgs&GJkiUNgD12cm5Yo^OiWa&YThP8uTA{nJG zEXrhx7;xs(sw8XXAq{uTOt%SrR=~T=d?}Fw@MC2=W+FtN1!M!QXf9&~I_kR(eoR#- zlBtYk)h6Cd1>8d>w5*(n?@p;wR*`$jAn;HvYTrY(!eWe{qP8DV*%MU$6gB@HZFq`0 zpQ08Re1ZZHBRB89b@p5K_RODtH;>wvTHd=cc1ydN`hz-;nhIVI?kQCH@khAOQi1yl z%>mp|5HRk8ry3KQU3aPVLhFs1TY4UMzF_>guD~K@D}-ZQx6)E1XwT!;7Yv8D(P?3Y S_Ev(gPJ8U>e>2cC1^XZB!nq{? literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/fixtures.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/fixtures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bf05c08b49f63207adf193c79f8b2525973752b GIT binary patch literal 78683 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

ePGFA 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/freeze_support.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/freeze_support.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84b8ce8544a93aca28b37fe820d9a1527e981699 GIT binary patch literal 1838 zcmZWp&2Q936rWjp?fq;>q9lAtJ5nl=m6Gits6`Z|g+?i;fKWqw2#YLZ&nDh@?PX>h z*p*jB6^B&f0GGm{s^CN`T8Sfn0;s1bs7c*csssmm3rZ`sJ@t+4Z9pAMGjHB|e)B$l z@BL6N7Z9xb*3so+7NKYI(qBqK;GKCOZX+8lA{*No7q>Et87yhVRTdRERab3g7qh_A z?d)}RQOhGcmq-22&fD7c?4sULs)g?9bIf0LY|4pA8i8jC$M=Zv38QVF31Ye~aXdkp zXS$^2+kp$34WE&KJKhp$FiN{X6ol`)y!N|vv5KRj>3O~|r7eCI?zvN7!4y8Tk{FU5 z0z?oL-gz4c2uKEldPt;!A|#|nyh8K* zrE2X?G?22=h&U^PD-uxrAZ*vrj)tB8Q_JvCIC<=18M7OG3Q4~02&5n7@Su<2<%y_XAf>>V-C|0m2e_w?!AvpKv= z_o+|~^}hF^o_O$`+ULM4d3Yfo;xI3L`x4*A7~LqU=*IiN(_i78R8c4My>bOtv)va7 z^eORIp|E0^rnm&0%HHa?RE$f@^Z=C5UV&!=EMx5@2VfB6GzGvEw5Am>4t%#$E5srR z6rf7qQ&RjbwilrK!AyqaOMCChKMT`K6r6xrolzD;X#18%KW zyBPnQG)#nR(C=EC(S~!iT8J_}kM#D+QXm{RDmXASxG+77MhX`!${9x66w8JoDcP?; zmE!(N^TH%_B2_9yYSVW-FsY}BV6wza)|L&*ZA_|`QekX2!bYS@v+`x�?bT7MFmN zIZGCk*Z8G)lgQv-!}bFG1wAv0vTyvX4n5ZgZ%%wQ^Hi^F4UB%FZs*Y6%I4U_`q;!; zX=`l!?(tineWPzq9$B9}@@Qs#a(1n;rfyXZZB}O1D>DzzKb-%u^6q->z#3jVd~^R# z#gSh|4&7z9J%)yMovVRK~RCf*u7c<1u1=EffKQbD8V@wSGB z_uV;sYkH$pd09l{*xy`s+~+6up4gA8899996zt|@oi9LcFG3kmRb#Tnx}(4#ArYN6 z<+U^M6h9Ail0g|St4OtSf>oa-268a_4BNM$CY9h{^FBWn){B1_z9Prb-)f z+Qb?B7O|3~WnQD)v80x>>=0b#6z8+BZ7Ue#ml=!;e`*MiKSQNo(SaRJ!$aGnXn15h byC0LS^7ys_=WnHf_zp?XhX2rk6Wjk6`FrHy literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/helpconfig.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/helpconfig.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bc442e7849d3847fd6409872c0ff270fc098557 GIT binary patch literal 12255 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/logging.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/logging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8286c54f1cce9e5e1c8995dd1b1c13c9e51f9cac GIT binary patch literal 45692 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 z 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/monkeypatch.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/monkeypatch.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9172fc8766da34e3b93741e268906fc25809371a GIT binary patch literal 16529 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

PbxC=yrpjM9itO5h!$wW_!Y zOZyh>2C#yK=<+%=V`F(0PM zMH{p%5S+?#ua%1kK&a}mR*fk%;cI9$Thy4jimh43#P509aLq` z3wc)?(r=IHDsm`XwOf@*`Uy#N6DF=~$h#8bI05ckhzyUgTd&4&s3JmlIy|IJHrrt$ zMfzjVO2Wn~Xr*!WY1%RbT1jjvqn8e%mm$i~m zFrzi+kj*Fzj{X^3ANLD_3l@Bf*$Ik7&PT3J`px&)!?`Y9jK++?#4GE-5B3muB;#^+ z8KOI8D8jDQ(7lux%`3z$K1SnU#F_*ankzhJRzx3p1`N=4n67kCS)#nDy);T9LmLO4O_`i3p~>-@2?D9J)z%2gU>uwe5mAQQ59;~?qaDrim|*8&B4hBJR>mSpcAh_f zZ453B!4+Aw?Ixv;wp~3vJ53(&Iltz*R|wvM+HD#^@*9jD?mv^Vz*b~lRMItUqecB3 zBKq;Yb1h^S&aU-(LmruMtrrj}6LDZYO!4s9{*X}Z+h;tO7JVEIMMZQQ! z@PR{dgq1!@N`1sAXrAJA7oFs{Y2%ik2)8{OputUI1Xiq9n3Boso_rsHhM9$$zzoP4 zMr<%&`0D{-{isB*ao2f)8@Qn+MDb19sS5l(ffy()tl;b#?jrrL#W-A2ajxt06Vx=> z-Er^t#Bd~*Z`yQ?dMGibz+Y0;?^7R&(&fjSsEtiMn>YF0TpmA0>x+9k`6a`)(KzlS zfcp!Z8@Ru0L^gM9VZ3@PUj3SC{ZXpvCZ#<4qYTU?2FmKO#ByDxia&-Uq_+O*pz=+E zi!0~^*b^Ld#dObak4{;EBdZ=2r`)87-!|jEvHh;c4}5yh<(sxnKYas;axLBG2hsTh zf~4#^|C?j~VhoqeG!-ohmdc_j*9UOath~o>5zbarU*CIe?{xQqAhxW{uI;+<1nv&r zG%PMyZDqqE`Ymhzs2EoW<6M-tlIupz8WM9Rz7Q+xX>TB%aLQl&84W?E0ql^DWjSYy19&u>Iz6GmblG_O{54Ojt|wLBb|#g-2as0pLM9lx#qReq-AzD7sAmfzm+Bu1_sqaTmMNt3Q*)J8|Q z&0u{yrGWulY6>UA)&bllj2AWvEv{dZgFMqAA?CeJ*B;RU#^oKd)1X?9#*}J*&w;b< zbl?nk0I>Vfx+M+sffY-zfv~wmZ$x8_8Kz02QL@>k%=}^$9F;ar%SUzwXqde8jY(vG zK7+mj)HvS^+0!I!TIS5DAus%K8O&_Xim%PVAAzp{!6^-l=@@e5tJgDC!=Qe3#(T5@ z)}uOV> z8ajy&I)XE2x@!_95C{d{{CO8-4YhDYLllF9o-R4imvT1%@=IYZsoX0H`c?d44uvBh z-H(e(cS$tkq+it`kYSS!U3|p#opcP43JeV7Z+7F=3b zSXh(zwEAR@&|I=# zn!GZJ!)}%gFrlk;^;#30F&y{@`dXvMm5(k8fzv}-{=g4VtV8rItvpUB74&N<#H#0Q zoUl3>q8E73vP$oGtx9Cx~_U`#)4hFUug%mikL$Gp~dqEYrY12P~ERQcvB{ z57(Ll3h8XF9~*ud`7@fru7tQcFknQUUPGV+ih!@?Cr*zhTdzZYLO6LkT2%=IYq>9qfD#yD={rHw>|1DwIZ` zvqgVT38e1Kxs9(_!4XlB6qgnC{kupo^eocZ(McTA-hXZXTt)ko>z=P+=ID*jynJlV z*8$T2a&R}>d}Z%!W$)WPbCr*R69Q{=bmlYHP(pg+;H+18k%WhP2Cs>rYqYsz1TFP#k}0P&3$UF=AVd7}M+i4VBc~7rMEddpyxi z8s_7X^9u7chmIK-nz>Sj=AlTUgBuU5E>uaL?(=c3yPhz2ur5Mwx1Ck@O+0eOh2 z+bCeGO1qD{V-{2`<4-!`578jFLMuY-C;M{?&~!q-TUvSfDErClm5UqfkC3hxe$y1= ze$y0#7P(=8<(873xD08Fy$$w~g)4e8~HI;eP}7NZznx* z)e^TwnjoeKQUYNpHsf?`4)gDDYB1SJ!iId2(=5iPz+pRXX^o z5%^$es!6DLWCeeEfWI;y++6vxOgI~Yrz@u>$QrIzQb6cCSE84rzrBEian&ml3E8MM zRGirTTvfg}Lql3E!j<_h;lZT+l_vhD>}vE%bZJO8pd5by4U?GQM>-m*@g{1(IP(XN z6Ahz4GbQ`<>iQ%yBqdRnF$G`)aRFotr5QuiRSir$Ra+^qjL$r7`UD3`r+pG?8>O2r z>x#rw1_4!oIl4TO@GzE}>BjK)UisfU;l|zVRMmOlv4bZdO0vH~fhz z!Tn>QG~5?zScpiA9+B<~^*DR<_c5(aEwwHEZ**P=vkw9;Yk9tbQ-An5_#g=K? zbol!4wc#ZJFN@ELcCmD+^q?r75~uw$r(b*O<)@Ye_PTW3ViR3cduFV!IbU`z33yp5 f@rkZ!k6sKfOYIXPI_sIPxL$Lu<|BdL*s}Z|fl5J> literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/outcomes.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/outcomes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e83501f6dc59f4345a2060ec3be783885a8846c GIT binary patch literal 11808 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 SDuctPGQQw$m;hL|;(q{+DK3cs literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/pastebin.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/pastebin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6f1466e8ffe7468da92e66aff72ea33a5421c25 GIT binary patch literal 5677 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 X=9k)U+7R5T?Q}>bAKED7!{&bhz?63K literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/pytester_assertions.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/pytester_assertions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6ff9b08689c802c7ba631281ec8b87975ab13dd GIT binary patch literal 2503 zcmb7F&2Jk;6rc6S?s{#>_1oWj^XARR z`}!rDH4r=xs}~l2))D$sFr6nJ0cH0)0C$m%O2}61n5)!crI;dc+>O@~rG&%@H(66l zs>DgaT1it-O8)Fr^xaQw!&bjcmh}BHf$j8R$*XxgW9u+KwW^uf+Y^(-trLptZe!l@ z@SG2Dl@rDZeHXI@zv0^0^EsX+*!3-&*x2{5!x{E(dE~GWBDy9?lR)9xl-=6^;L4P>M1aa5Sxc^|YZ_NN zxH zL5bzIEv5zTSd{~jWiU9)Eyshkbh1*1D1d|fUKid59%A*O7uQG{myn`bbV?geXsOa zp?72Pn{!W!Z)|+FQ#|n`VxMb?ZgqXKWuSqP%};((w{!2WPd3xVjg?>1!&?dS!Lgl$ zxt$)~1-odvD(65u7i>BNZTl4?lLyjz5Y;(${Dtj?_pVsi7XJp&hBAztpf( z_tiBc3eg}DBm-4S(t-AQ|7%W>emOfZ?DPa2InaW18|kl|mpL#mBj@QbFG5C;mWdIY zLXZvyREV!G$ymUA!Qwy&W)PgtoFhs>I|Z->IbuN(==B=4S%_ME z>O8Iy%VSVLu_LjMU6NTmKKkyGEa7?@aiv69TZlrf_ zX#>sS(eHzAgUzYO%FghGul0@oZG8}g9i-*Aw4vsR`Ec-u!A7c!=9)^Uby>LJ?PNnnqh-t`6zCF(yb(h@ebd4J?JH*W~yeB=-NN|Avx7JPR%`onG zJPM%Wuqj!ysX(Dh32#s@OicS8G4(^6J;Y8=n?2F)yKJY*Z!k{BAc}dkh&1@BMWP6j z3(H89NQ7fFLj=?d1I)nr#4e)X8WH2ef?y!fd$dZ$_N$_?R4Q`~*fca53e^$Lz*p-K z(o}TTFxM{Y<5V_ZMn#XMsvx3-%kcXVN#8^Ps!!B>yWx_HbQH!#ab>>&X~h*q`5nFY zZw4tRo}qzf$ovDn)q+6kX~J75w(>>gbSsv~sjav`8I8zh?oA2vhJ53^#{HTE&wJZgvf};_!W%$+eR3g|q-;nn1cRmJ-MiC@oM%jAjLj c78R%)<@#EAfeI);Agw~TQjLFlK_(CWACX{KiU0rr literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/python.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/python.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b835b63cc53c7e5ba4886842735813d73aa5f82b GIT binary patch literal 70410 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ܾUL?mfiZ zYc^`!|3opcCH?zyIXd;8gF1}JpkYKB9;C@+Bu~<%R0JwGy14>GRmF_7vM(7VkO`U^ zP9Z}jEw?Dpd>b`FRX}Vb~3%{NZNhmPHENMBThBMpJ7oYEKrN(mu|#MK5fJy%h!<=g-8l* zOeExGBhu3gl!5H+_-MR|5Ut4Y8Emw$0gyh0r0PgM{Qr86G3il$tK6w>@YS>P4 zGH5GMY0EARiqJ6Ie7b!8WObLbX;@p)KPVM+uRK0oMp@puofp z^yUq1dBdK(;jtc!q=*(u{|dLf6H5wsHmNY@ZLpe-MPlzw05G!l1p1*Q_uA(JV(Q>`^y^75fJs9Sfo}PSq`ogSt_4}2z z3wEKjV!Gl=2bR+6*LL0Py0zu^kA45*51)SfY0RFf{-2tRhYUqOA@zubQlWCoLYd$z z=kr9rR*2cgR?-guub1q5LLr=658o2Tvw%%BYx#vWrr%NJSkV*}6p80qs2J-YYr*^) zv{hz>w54##g}8EkUM`qpssyQhP$^USCfql|oRE?pDY`T(OGZPoXrIRk3l~nW)xS`{=F;~I*=u0Wbw_L!^EXNtv z)JwQD+1iFez|+5j3UcC{h?Z6#V-|AakG4_iAa39th9J=a+4C0^1xFJkKZRM&uhY-B zD2IiFmx+AI;e*FdJaC{pw6C}Kfy4VB>^*P_dM%>J#F|oua@u9`UTk%<4K{C~x+L

#inpd$=Yx!6XXx5>gDVytWX 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

J^Q5 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

F=x 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 ABme*a literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/recwarn.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/recwarn.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c59ca956812dd806f1a822044a0dfda20fbae7eb GIT binary patch literal 16063 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 r7o5HiZ%_Rn%SdPW literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/scope.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/scope.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d054835b01a3696ae11d6085bafb9365e1a069df GIT binary patch literal 3728 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/setupplan.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/setupplan.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99f1d8df03cec0e41e3ea2c3a196405a0c7fd3f8 GIT binary patch literal 1928 zcma)6&2Jk;6rWwMch`0tn>0;`2+ih032y83f`n8763Pcuf+Qm4VvJTB&)8mPy=!K7 zlQ?p!M5>hZREbM_+C?olK|pBtf;&P>s6~WF zO1@wb$FPLs&*ei%4;un%qMWi6q%c|YD&4Fm@VD$;=v@xggGUt_WAqw7;ymyTz}7P`9{h)Qj`P zUH6@dEBCYwS6j3UcfI(TQCcTdn|EqXadv~)8^zDfwW4c! zB;Np)Q6X+oZ#3C$?)2!GZ8U>yxT*KzqSqjd6nyR-n08USZ{YFrqvfYxyzINVyU>;r z`^$UFZS{Iv9cpLZ>4-xAcqf74gMT79E}wx5y3Y#4qs_8OU9ZG$)g3I0JoDcHfXoZE zuI9p8yXX<^*P;wxFhg_(Do44v6XMo+<%k%y;48;kNbj8hUg|%+R$1)`OSyO;hlLZ! zVV;6SkFOw6GD=mVuajnw<`CLYnny}U=0Q% z;v+Ym{|X%g)ewB{0!+K8lSFD}|H0ma6J_*R89h;^UntY<;qjBV_VG#y6_lt0v!gW%bG*P*|mtXQXN~S zP4v29+H?~16U^`?OuOi{lHT9h+c{BgyijhmC#DbN$LUAuSL)a$9Pt8CWWkHFmK4Yo z`UHhnyX4p;C(}{b+#WdZXXK|-71 z@18&GK&?8?x>;{nywo^(KO$ZuVOgpl)*b9ye9dt*_jKV*8B&2eH|Zo69@S$hD429 QTy=XRN|E{xhIm+i1H%)``~Uy| literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/skipping.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/skipping.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c3995300ac19745b2dad0ba5d92464fe230990c GIT binary patch literal 13623 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 MJ=kgE^oYrS1Ay*0-v9sr literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/stash.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/stash.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9a287a1917de5dcaa5589b309906f598928588f GIT binary patch literal 4391 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 EABs=l$N&HU literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/terminal.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/terminal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fddc643c7fdc361db2fd0ed3aa29e3f66f546340 GIT binary patch literal 75746 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 H5AOd4Y+1;T literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/threadexception.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/threadexception.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe63514bcca6dcc066df0f3eceedbd459a831d2d GIT binary patch literal 5202 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/timing.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/timing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35a39b898498940101a0b50ebd1f918664a9b729 GIT binary patch literal 655 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 F`@fvs9s2+P literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/unittest.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/unittest.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd4bbb5e809059b125815917c3b5149197e9d305 GIT binary patch literal 19224 zcmd6P32+h$Me4L9>#)s3vMw`;Bqug34+614i4+Jh-Jm7X zWGp+*8g!fyHE}{}JSoYO)I=(4D)P?mMzdEe+q1jb-P!>{+JxHFmR#}FCR?>zv?Wgz zulM-=*XRa7!Ll={t=+cu-jDbGe(&`={`>JixLghn&rkZc9sgfDIPO>ULx0Q~z~AaM zaojW~aY0VvB~z3iG6hXM!9r9B3M?0cBFoJ|Gs`VO3vw}P9kK;&4A%_U9<(#q0@x9B zFxU#XBv`^=8(?S9$zVHRSJ1^^2jJ3RDT7M@yMt~93sFz74E;ExeT>ZG1$o9 z%BX*+DcHo|D!|RbW(HRSZV9$9xF*^*FNf3Cyod z^HmpJH53R2crH>Z+t@s=4z6Z-TX0Rp9dSlnk@i|N5NVHC29M-g(xHBV)jFkm%KJ@} z2G`oT!vCaz)bP9*Txa4Q=A>oMb5diZ=`}%XBeM}jwiMUJ^dlP`Q3iIKSVTK_d9vvo3*Wt&%yerhbXIJKh`&~OxR>F$A3i4i$+F!GHN3=k#u?szmB z>C?Iu(d`fl#mK1vsucUf1N54xoER8JZa$SQFn348ih>f`?p#NfN8-{58!7v)y|MoI zZ?IFifay21SidRJ=Av}hslLcC4ent%J~A8-dV72Ova1OUj-zvo-~6pP0MpnD2`XjJ zAhoyTk$BoQ6G9?;{yA>kG%gHUmZ0J1YuUFIew13J@vvvmsYAJZ+@$Xc8Q6(Zy=JgX zhjRHiH_nfnE}Qh-cZh4@5;ncQm6HXYdz>5PPI9M&$GMX{K3TQt8zx{<#aMVKq6+b& zg8{oLD5HvM9b!|dsEz?e89CZVv#r_=W68S1N<_6AQ>a=8V#+Yqk1)eWm4qAy?8Jsh z7}zEKa(pN>9F`-oL_k#Sefo;ajTocsN1~cW5{d}GkLQyU-P?{lszhYv$f5Aik)DWh zA`u@xa%ez`_`fX22hp2yWOrCebbfiS|0%`48>2gMWKX#71P(_}d@z2b{RE3X4lp~?xQ92iE^>6PO9#w0#GE`;96&ebkh~Tv$xg7)Ah<{2H$!TuU zVl#U`aCy!=G5N%dFYRhgnr>S-k8h@BMn3=axu@scZJCPdMS=6Ix$Wd^?s;o%+FCo) zGG|?uaaVj$QaxYNk}hevu=h&O)xK0o%WO$kO6ADO_8@Bke<>@np9Wf2#vU(_DlL@?bb7i3fu*J9eY;Woy2L zVyClwL;M8Ka~L~qr6tdVDPh%%#!X-)qJCWHI4115I{h0b zgmFRHfYywIs^icR*RfcCu3U`{Ex~Z><+)sE2CM~7u3U!(%XKK14_4-29l{8!bvT#n zt)sRkSE@rgDdIU3h=-0lAxh>6^SC%zr{m@F;{k};XD}xeZoF&f3|kqHf|qWq1Wf~K{{=(|>;{fQ;LFWg-J$gAO`{2jXEVBCEDgh=^P$ba;1w*GTL-V0}`RI60ssB2q^} z9tcM}bQ&0_l$W7e#(=W4*m^7iQ6V9#B_Z&0V!gu=NnII|Bgd!#Ii$ZDQX+|w;m}Yd zaXc=C`ZdIGX($vdsXh0!F0o*M6Xw=Y}ll|I~c5b;U%sF?kUma;@$DDKBO`G$KbJ96)Yfal)=WQF)wvAVM z<~Q}EH}%YJ+Wqguzb*Mi$(-%*`(<_Ct4~@owab#F8Lw~N+m`mWUF`Yko=bb)_1>54 zS@2ZPi05tRY!_N*Ju8!)3l+YZ^)rJPTjwg)W;`_u<<0Zu9qICpi;1hPsq&85@&|wE zd@$3{G}*gY!c}hmu$1#GTc~Q7kt;vTo?y8i#d7-jq`iZkoOoz^f zQpHbUm=hr-#Ui08Dn;u;7t)BO7PWv`rNxTtA`8Ln` z4$OKFfcZ8*#4nmTPYWf>ey0$xcy8DIySDRvbXN3-SOH^q9!&QO{0qVh;tS>%EYn;9 zzqMzQC#Is4i5JZUHDIzvjb+k&T9~w)=0{BhI3T9a8)W81L2@SfeqORX?|f0z^$W?B z6p~`noV4_VVL$JjFoDeGk25=#VhnB2U}yS>!JuuE{GcKE<;q{-erSSJ$_+B@HCIlM zWS1P5OJ38pS8mi4C>=X=FnidcZTQ0@iFo^n67gq`xIYp155%5|pNRNzavvRL!Z@iV z4t=*LB4N2Feln)jtZKP${gyz7>d0aDs;&YY)h)#ta(6Tmj*Sc}eT9SMI7kQlE#m9b zAb*$m-b8t*oDdLDBTHLCpa8K3}kPNVzwbegYB+$r$5Y4|j$?W9k!*aebyIu}nv>1t<6k z(*(r#v!<6EBGd@_oj|$vj0-x4l}snVM`ZK3APJ;!Jjdr04oL)FcH2CMjm~p@0=0UQ z3z&P=3Vo(S5nc09enZ=wagVVEnk*t)el{OS*$Vh0vK*I{fFN%JswzYyF`1Zhz^vNQ zPG|t?m7?+?RlqORk!xDs4J?JW6YYl6+S=3&%ke%Cbg-tdEMaHljb-eyKWMq`(egNr zB!mRy6ry^q|ALTOzU^wyY)xl!@AZoMjHl}Q`fYQy+h4DJwe7;OJA~*Odx{1n z2bPmbnhRLLt~DzJdt0^0k+1@B!Val>lRX=^=?1}lR zwsck7#occtQdMoURR>a@0~xnB<@PU{tYyCI-rAQRzE#S($`?&$S4XCzE>*Gey0`x2 zeeZj!U$$TOw7uhLzjM2it7-a(69~WV<-OOvYv;V{Gv3-oD~kTdhvi&ZjgI@NvKiz1 zR;RUV8Go(B+PRjyRxT1;ZSN}KuKC>rudVH}32)j2z;Bj#y6S{C>j+jG^48H{GP)=j zQq7E59lm7Nq%V;SPI4Ez!;pue^!7HD0ju#4DLQbGUO}@}2P6`mZL=w6TVaiUzRKE%Kjz3(G zGK2E|zJinm`kJjkkaCQD?TZUihAMTJD8(42re^tdVall~OeeY#RvjzOh;C!b zzi;~gj1dXXRuHp*4U;Qo3DsFvjy+9^&wBBO`1x<>s%V^4gs(_Q#hP9D8y<9~&LDI-V>HsGzB zuV_hEv_Mq@BiJ3Tc*l@F3a*sv{Ps{Tr5#X?78rEUS#i z$TWXTR3>HzX34{-RLIiyJj~o&dDZECABh%o%PmiX`4Rrs1Dnl`+rxalStIA{4Pv6k zlhs#Xkwwu9dG_c>VVNb1H0;qr#4L$42vUoKmWUOqFsDnDL7P;W!mTbD@Hk^{L@r}mUfHBt%6TuaKFC@Ym}B^PoxuzdOT_+2NJK5q_s0M+Meqz1{2 z(lYet!F%P%%aB({%cOGTUa3;5Kwhc8AvLn`dNIbTBzKArR>LOlmwWJmhkM7iL}4m> zBI|GH2=&1@)v-(0zQ{B6L>CNHM#+;;V(Cfvz}iBSBHijP0?v3Q?cxG{S=3yR5P`-X zop>syeb5*`!?-{kdC3FJBHT~cMGtZELA(G}5MCdhq(a7KE*@=D`BASKA2$HX_Xs#c zu(#-n~GZ9g8Lb8kx7Hl^H~;8r>F^yJfLo|$}R zCUJiB+~}-3kg^3dnZq#T;B|u8{P_S(lZOEL(ntR)ULHf0ymZ`lW>)RPvW@xf9yBS? zR5yoL6(^F@+)a#bVsav7tG|0dbYR$k$mB@I0||p7CZ#ZIFe-Y<#AvH(4}~;KM@XS&vnn}58LPVMOPE-Z0iNPT zatEKWvok5q*%}vYu1V{p>$~oId<{(;eNFKRezqFr`O5-qdUD@ZI1B8Tfj`cDJx}dr z^8z=>tG7r@!H{jvySumO-Cx0;D4-~i zz~K?14T@)ihojoqjKypb#T?x^=QCNVidrKK8wX6Dreb0ryl|o=X@$I!PR)X<71&i#m0c%sHcz!O9!|4AB~-efx3QH+j2qM+Z^(@ zFnfUt&2nnZN&%{wi2s;fW{P+cro0^h|9tg88`aC8I#7i>635yzm~DGFy7 z*=AKwe_S34Cvv7#)dFf4krQ$``d7ul_&|&qyVSD%kzKh1qmSw9oJ*l^V`TiKm-ESehZyLdQ^riacB%~!2YSFQgi z?dhtA=Bs+sRlOOH@0MVyE=@kX=z!sA+I7~I?96zo=RHknPgAmUv4ezl*Q9IS=1<%F z7kn2F&f3<^+qR@_TdrE)u|0I>c01>({fMhXCyOSpi&A%4)k4FH`Gzg&hAmfpsfM1Z z-Ef3WKXUewOik^giLc#w+bmXA-F9%5wKL`CtIt(msCuRG%JO%;TkkB?)SoXsS30-q z{#5lg1a*}8U>aMfZvBWeqg5D2@cNp}n#~sr>s1FaEeSm)A`+ZI4+t0Pn`|eBo?wj{@rF~tqzMem^q7l@7x)`AGf1-R9vv{V{ z)+KOn3ie%_OGri^B&?B1P=1qX#7I=b1fnX~S)VFP&;x4!OZ=5Fl z_?uFmV7EZ9m#PZ_q=;iN;pGYd79f>l(uhDI;&yLJpi@P;CeP}feX|%p(J3Q~lqf;- zOP>5#3r5Ec*%fq2px~#M8VuTD>XpBS_X8IB+sI@Jwb2fVFr*xle?&ijisX`@X{Ikz z!DS>0KAAJNwhcR*ox&n=gI-mFX;Hp}wB<#zo|VwP?AWa}5F2KE@7P-J+=d?$8FcJ7+p z-dQPJ^YTawmWam6mS_b4CXX3Qgo5l-IBl9#$+M4_|1nSsKiGk<+Kq5LMDLB&-F1|6 zm53ze6rw-$(S_flNg*^e{UHH`4f9_E`20}2LBe11AeN`VuL0h5PcDQVv4ZWRB3e%_ z3aFT)ROd^%-S{}fHho`dCML@gbCNcjO&GIk(}_Ag7ehHC#rAsOenrWWo;n>*`;Yjr%*J`rX5W31C(prr7` z=bd1~SDBk3b{h3oipJvnUt5oZ=oW{Zv={_6CS-QVbL%1hjOxX_rJzI341GomQ1hf=en}Ulw(;oOS5^KRHHl zSdW!I8iQiX=G(7~^pW3q%(TtFTmDmg4VF|w#zdUYT~dbmI?$IasTWI{>*P?jkLAS{ z^)9qFT2|s9SXKw;Zb=H)OUg1%_te1|%X!y1*VHp`o28vAliiCVTXoM=#Z2T~XY22C z7-w87xWCxk*(h9F#v?6=T|w6x(G&p`YL4>vP*k8T64}E7-UM(Hh;=(lnc z;%XZ~o(zpL_d1MV#uySt6e@VUnOTdoTLq)etHW0s+G>7FMMrM5K(P-?=R83caQ3iZ zsUofFGo2-ozX!f9Cj_iU)aHtz@aWM<=%gGT&gzK9y@?Q1yAUaT&`%Nd01vjp6f!up zhoz%hJObJK`t$+oIjju1FsYnL32lhv6v#8g%7^jJ*y_7z9%8x^IKMgnlO7NCI8KQk z57ooSM8E^rxE5K_Aw`ak96LtgkpBklY9>@#O4(1zw<-BYl&qp;I}+8>7mxK198>ME z2glik4EYyS`5mf6_$LcO)!h453FjRlsXD)AT`>8HIWCA;Kr+{~PI*6m6%n4Y-_=E5 zK^8rVusm=RTGr09JK^|YUapj_1-`ELZHQi+vu#2Mkh5Xt@cGBiJvLXe;=0p2)AO#g z>2^8iU54<*6X#A`-Z59PHRarzs@S?v)pVitC#zmvHCxs8%c}jCpT4^3=iC2!`_CWz z>j!7o?xz4pW~!cdx2D~#7ap5)cf5WwvwFk)>h0;(+owcG{uEegyRbUl@z9*7^R|t1 zd8hWEn}Kr!7rj5Nxl}Xf+yH6M)ecYG1@Rrv@(&n(>rVo&2Hx?kx%09WZ|lE4VJ7Ie zI_y2A+}ou!Ju3=12pt0a<8RRxV4-WswFh2R?z@mGU;@k%#R(G~lwzhoiH8G6$D&|) z$Q&RE0=pbg;J=^_E2zU>0H0+ZE*khq0?v{rcMHMEY4;G|2GgCS0YNqQ%XFW`-XA&^ zjUPowpS%k#AQpNbvbzqzrn?RRJ9iN>w!n88g}vnNqi~!y5s`<}+)UH?mFHH@``XgJ zwt3&$v~TSd=d5pMx@xC(LQsv;@liK)%ELo|V3{52wBD7mv+)Hz)0x%H>m!&sQ!_S1!Nq+zBg6 zl_Tl+knXc&YMYb$GVa=xt(JX)97hv4d34@BDbpvqL+?M&H7PPh84=ruMwMNs`pCXT z)~Y-V3u{Hbj;0u{tP(U#$v2QdxMQc9)k{?R&kO5$`5d9y^3X0525Y9s3z|~9wAHcZ z;<4}@hvQN_>xuh}LA{0Eu)`1up2))xWm>(l)jBc!e1{)UG-k)NT@ zI!y^VcI9U&`4$q)ZYb|I9~{3gP^k@JkeKPjs60uS{gjYfL;gM`WYCcRijsdy$yG}J z1ttHAlAlv@jgq%1A^OSonCi-Uj`||eXefje#C9Wg759*~6MG1il7EkXiUl=yzRhLI zDo^jr>^X3HPpWj+ta#uf(PUnYYdx0I4^6ze;g$dhb})0p#}>g{^RdHXuDj*o&8}OP zCUYmhxSO|`muG4Mivs0acC-AZ-F3^1--})$TS+-pB4?GzZ#hJB`=Xbtu3fa-&FweK z5d@ALW{AqxMJquzuFT8oAh|nAZ`+8AK)ab29TrI$2H<=J4hFG%aZD`{j5*{(K zVx!HUIqfbEvvC+wt6_1xWJr9XCh@U*Hhuc|C?~qk*rLIQvlEJe=WmtlJGp@GN$y@| zQhix;hqjn!lw$PsRWyjxOf-w}!z^E+QH-@^??YzAC3YJU{*Aa?L?V0^!&M-05`%ep z_w@d=`+qE4c=#uey!yzjf5V)2qs9T;{gGBs8~% z+(d}Yln~#RX|Sq|>|VriWLjYGcDhoEaQ&z%o;)6jAre8Pm|#_;GB(kO5CynF`$(OE zYGli}^Lh6-%KsJ3$LX{FB>;TZ2iCHw*14KhbJhS{zh`#7xHD7Ncz)-(o%3~T(sgSt zer2|9ZL;U310K*@0o`&K@UVS;7`X{@8Hk zhN6w^dZv&`kWX3WRi;b?n!&+-b?hbrb#Zf|9=QF#QpMui_dYgl9%B$e1h7Q1VSmeuM(>au=ZaXG5W6%+@^)6>W~O@>ui*IzETN8& zx)O0*-^-~8Xqcf+6A>Yq7DNQc)>Dfx`-PSjKiTo>jvqZR1qIH3lLG8p78-FQ{FP^> z_GDVuTpYMsm1=p2)Io&gn_b=fbx$LR%1q$GV;7%#WBcEO3Y6c0TIy|}!_KNNh!+N~ z)Lh;CuBVF*emC5JWdHeRu1=A=CVD&Tglj$?X+Xr^Ip__Pwb3|xWYjy_653J3B zTD@dTg^q_61R2IuZ=viwz^;?Z|Bad{9fJ(oF+e_MT+fWi{~d)0i~ldk*qum*#Y*Lm z315I&L%t12zgB@M&ywis&zNZWQ=q>{b9)JZ7WA4&qf;Hb|6|MFw*8_l-SOx`c_q0; zQp+}{%eN#=H+}WzSD(YJ!S=MTJ?XyTUU9>@8UMfUtedOH)XzD$B8VsDY{6>n=fA{% zER@>2`9(k1xH4%vW39`PSTh(pYem?+rQ{a8!?(FlGI zh2;|w$$xSnaoqoqq1w@f*AA$xhE~mP0f$1n>5oMmjmM+f1&Q?-TfRO`Rvl*Ad7ajW zqo@{SWD+B4CKE}PEtJse%0&HS zMD}sAgOU;?P`I>huSJZ?e@11cdYKm1A`|z^FC-h9?4m-Cj+worqOeOYxkl-Vv?7zQ zPo~Mr;m1c3XdHD!IW*4-$@`k`%;;a^Lvkh6km**Y70!(Wv;luWhRJ74-c+6VV>NVx zTZ?2V1!2_w?8-6vvn%W_p(dyqjIQuvx@yf58u>i+<1&R1Ze>;tW85Da6BH2y8 zLkSTHEyVv2LHALz28n9hp~Xu-AeUp0;(t!rjue!W=ileH{TYWjDAc{r_f$gDRA?yvMmf9YpKA zP>~iYrn;uSk`k(CR{m0Gx*@i|FL>S)D}E(50i~iQBifU#$%(Y1VP@iDWWJ*_-O-tH zbf(2F)TA7~v{;8>nj1b8gbLGX_oB0m@8&b^hDCt^%U5NAwfAYjO>@mHGgaMQ^^l2o zOx<^zqx|-6@u-RSO`W>UQU2jRGiP#4ZJOh2J~CDC%b}f{5aa>~u`_l46ju$K5D;4D Z`09^LExel$5XJ)N_5t3+Z)21De*klQ;>7>} literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/unraisableexception.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/unraisableexception.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e629828d606a5e9eb9c4e431506bfe6fa2eed403 GIT binary patch literal 5265 zcmeGgTWl29_0D6T_IlTAWa&(207LjCADcXnsjD?3G% zS}9Vmz4xAb?>Xn5dtUeK{wWd(6KEf+{o}uFCgdqjs!enOuH4TNGD9??aaoe%QXGdc zpXF0L8-VyHK&^O zF!ci*%tmr8sTPj#WQb^?>qHAb;9ZGQQLR;Lx-O<-JkgsoTdM-sMy*+E{&l2^57;f& z#Rrw`RQ!dhqIFblFH9AyqiQeliTFS8{^{Qg4<8r~EO4>r1 zWc`Y&=kX586F6+ZG;IBfE#(wLxujExp{I>YQjJ3LD5QdgTUN0NyTxP6d6jGYBa z)U^uBip1wEFit<)Mm-nP_PA@QRSnyhkQ7T&b2S1a>H)oIpd8dTiUMM9ty!^VudUuO zNR><`SImLSxUNnxJ+DUH4?Y5i)Z2OmeLX+mRgWhrwoTKc1zVR4Q_7kK`XkuQvcWe% z+v8yMyqPv^k71A0XPe*&x-wDkJ+6}^7@7*X&ubW50wG*k0su4O6p_76HuJDcjd)5x~WECj^vRX0du9AiC9=ajkj4m8MbwhlwrHndIE13sj zmW7z2;xL&ZQ(V2GN`yvWIl<8u9C+Fa1A{H(AxRXe93RuvxBS4OGT54)X zqthtR(+=!Vi>~e6yM|<0hdfHb65GDg5*4hyNH0Nmm3-EeE%#D) zQ#XZ2!SxH#wi`{ZA}TLA;MbXE1SVh_l3~j$yQ};g1S$=Ol!@tcfZdxg%Rtgx?P}#BMzH}o)DS9lO6bG zo%0^SV_?q1^pZs-IE8M34&0c*A%uf;BMg)4@oH}#Q7nD1`et5`rX=J`Hz7j%ioNPq zH``iK8&tO>*m92xV(=PDPM2jTEXz4lE5O?;z<4GgVopZAi#mZ#_br zp}SAmm6?Jm#t-W-aI7QHT_?+Y+!rkO@xEcM929)L<*?vuUkZnO8=u5?F8N@Dc#Cge z3Lq3Dt-VVjgu*1;@vx!pCd4(1S_zbI1@U2#3+=#n6Ka+PBFxR3#dDUzp)I zbW+dhJU)c}b@la3y}MV?-+t-Eai=*0>ZNIJiu(}C+%@P{$q66wn(#|;nxEn|F@t{4 z&}_UpYZ)lD8`~P^V7N52H83%!G_*A^F}*gl2Ve_q4=R?JgbY2++w0s_LNe$(4b8@Q zW-WtG)zGH+DzECuF^fWJPpXAEM@rxdt4sNR>((nY^EI$;krc@l;XEmFJ>*;PycKzl zoQL>EE4%Bz5sF;GH$2(y_s zY2Fj_2QEQEaCkLGu@G_ukjcWR@gAuywdcMji%bjfuv<1ORp@sBe*iw#I}m4oCtt$z z9qM@$>is0X#K%G>xyQ||Pok~2rhhiQ(6(;o(49l`ZGCfXeY0)*7rLc;xx2af?tOFJ z`|b=q>E3^PXrW8`AoNlEqaz=m`qioVZ3pMJ9h~iX`FHzgJ6^jjEX3Mw)0yI(;%Duf z=G&8V?a77C_4guoBlDffxz5^T_gv?0Z)e|J=e}}4T-Q++{GIV-;%|-L7QSd9tz9#b zJCR?E&36yZbr1et{j~dyzr;>F>mlLov!UKEA^XSTUl*yDHjvn+=g&f}?Ekd(gFUV_ z4ISX#1gn4K2EH-W=X=;Ez|zAVzM=hrhkF9BaTaQYK#?l> zyiT2<9yjk1uZF9XsK;Zw#W&phmJ(LJ9=_s)xZ|19o(UdET_T1zK)o9hto z_&d4(g1odJ&S;djtwCB9e&V370^$Wkeg(Tj(EYE(d8t7h8@@toX3?OBL5R^6B2_B8 z$*A{B%$r!jT}Z6M!fUJm#Ud156f8(6D|4~(bbMI*SrkhV#}6>18|p(8EF5S6yC8OZ zu)}iRX?3w6b=bOL;cr9~iwHLrV&ZUu_$?xx%V(LDnf92xjk*rL;+BKoqeDQ3FS>OP zx+Q_*xJ9z{DG5I&u|=|Zk#sDQ4U1&sB9R`G=u;9|_7kynQQZD4xRcwsOrR@Y_)6H*h{-FI6@e#M{bDrb&l?B2lmk@#!@Sh_rBM0W%{{j~wi|zmb literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/__pycache__/warning_types.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/__pycache__/warning_types.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09968cd7a42acaf3d0cb6e4a61ce5c31163c5172 GIT binary patch literal 6827 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 Ipr}Xx3(zMkT>t<8 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/_argcomplete.py b/.venv/Lib/site-packages/_pytest/_argcomplete.py new file mode 100644 index 0000000..59426ef --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_argcomplete.py @@ -0,0 +1,117 @@ +"""Allow bash-completion for argparse with argcomplete if installed. + +Needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail +to find the magic string, so _ARGCOMPLETE env. var is never set, and +this does not need special code). + +Function try_argcomplete(parser) should be called directly before +the call to ArgumentParser.parse_args(). + +The filescompleter is what you normally would use on the positional +arguments specification, in order to get "dirname/" after "dirn" +instead of the default "dirname ": + + optparser.add_argument(Config._file_or_dir, nargs='*').completer=filescompleter + +Other, application specific, completers should go in the file +doing the add_argument calls as they need to be specified as .completer +attributes as well. (If argcomplete is not installed, the function the +attribute points to will not be used). + +SPEEDUP +======= + +The generic argcomplete script for bash-completion +(/etc/bash_completion.d/python-argcomplete.sh) +uses a python program to determine startup script generated by pip. +You can speed up completion somewhat by changing this script to include + # PYTHON_ARGCOMPLETE_OK +so the python-argcomplete-check-easy-install-script does not +need to be called to find the entry point of the code and see if that is +marked with PYTHON_ARGCOMPLETE_OK. + +INSTALL/DEBUGGING +================= + +To include this support in another application that has setup.py generated +scripts: + +- Add the line: + # PYTHON_ARGCOMPLETE_OK + near the top of the main python entry point. + +- Include in the file calling parse_args(): + from _argcomplete import try_argcomplete, filescompleter + Call try_argcomplete just before parse_args(), and optionally add + filescompleter to the positional arguments' add_argument(). + +If things do not work right away: + +- Switch on argcomplete debugging with (also helpful when doing custom + completers): + export _ARC_DEBUG=1 + +- Run: + python-argcomplete-check-easy-install-script $(which appname) + echo $? + will echo 0 if the magic line has been found, 1 if not. + +- Sometimes it helps to find early on errors using: + _ARGCOMPLETE=1 _ARC_DEBUG=1 appname + which should throw a KeyError: 'COMPLINE' (which is properly set by the + global argcomplete script). +""" + +from __future__ import annotations + +import argparse +from glob import glob +import os +import sys +from typing import Any + + +class FastFilesCompleter: + """Fast file completer class.""" + + def __init__(self, directories: bool = True) -> None: + self.directories = directories + + def __call__(self, prefix: str, **kwargs: Any) -> list[str]: + # Only called on non option completions. + if os.sep in prefix[1:]: + prefix_dir = len(os.path.dirname(prefix) + os.sep) + else: + prefix_dir = 0 + completion = [] + globbed = [] + if "*" not in prefix and "?" not in prefix: + # We are on unix, otherwise no bash. + if not prefix or prefix[-1] == os.sep: + globbed.extend(glob(prefix + ".*")) + prefix += "*" + globbed.extend(glob(prefix)) + for x in sorted(globbed): + if os.path.isdir(x): + x += "/" + # Append stripping the prefix (like bash, not like compgen). + completion.append(x[prefix_dir:]) + return completion + + +if os.environ.get("_ARGCOMPLETE"): + try: + import argcomplete.completers + except ImportError: + sys.exit(-1) + filescompleter: FastFilesCompleter | None = FastFilesCompleter() + + def try_argcomplete(parser: argparse.ArgumentParser) -> None: + argcomplete.autocomplete(parser, always_complete_options=False) + +else: + + def try_argcomplete(parser: argparse.ArgumentParser) -> None: + pass + + filescompleter = None diff --git a/.venv/Lib/site-packages/_pytest/_code/__init__.py b/.venv/Lib/site-packages/_pytest/_code/__init__.py new file mode 100644 index 0000000..0bfde42 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_code/__init__.py @@ -0,0 +1,26 @@ +"""Python inspection/code generation API.""" + +from __future__ import annotations + +from .code import Code +from .code import ExceptionInfo +from .code import filter_traceback +from .code import Frame +from .code import getfslineno +from .code import Traceback +from .code import TracebackEntry +from .source import getrawcode +from .source import Source + + +__all__ = [ + "Code", + "ExceptionInfo", + "filter_traceback", + "Frame", + "getfslineno", + "getrawcode", + "Traceback", + "TracebackEntry", + "Source", +] diff --git a/.venv/Lib/site-packages/_pytest/_code/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/_code/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41f0ce192bc32c19b951ea9e0e65ac0931d5db43 GIT binary patch literal 709 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/_code/code.py b/.venv/Lib/site-packages/_pytest/_code/code.py new file mode 100644 index 0000000..fec627b --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_code/code.py @@ -0,0 +1,1409 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import ast +import dataclasses +import inspect +from inspect import CO_VARARGS +from inspect import CO_VARKEYWORDS +from io import StringIO +import os +from pathlib import Path +import re +import sys +import traceback +from traceback import format_exception_only +from types import CodeType +from types import FrameType +from types import TracebackType +from typing import Any +from typing import Callable +from typing import ClassVar +from typing import Final +from typing import final +from typing import Generic +from typing import Iterable +from typing import List +from typing import Literal +from typing import Mapping +from typing import overload +from typing import Pattern +from typing import Sequence +from typing import SupportsIndex +from typing import Tuple +from typing import Type +from typing import TypeVar +from typing import Union + +import pluggy + +import _pytest +from _pytest._code.source import findsource +from _pytest._code.source import getrawcode +from _pytest._code.source import getstatementrange_ast +from _pytest._code.source import Source +from _pytest._io import TerminalWriter +from _pytest._io.saferepr import safeformat +from _pytest._io.saferepr import saferepr +from _pytest.compat import get_real_func +from _pytest.deprecated import check_ispytest +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath + + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + +TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] + +EXCEPTION_OR_MORE = Union[Type[BaseException], Tuple[Type[BaseException], ...]] + + +class Code: + """Wrapper around Python code objects.""" + + __slots__ = ("raw",) + + def __init__(self, obj: CodeType) -> None: + self.raw = obj + + @classmethod + def from_function(cls, obj: object) -> Code: + return cls(getrawcode(obj)) + + def __eq__(self, other): + return self.raw == other.raw + + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore + + @property + def firstlineno(self) -> int: + return self.raw.co_firstlineno - 1 + + @property + def name(self) -> str: + return self.raw.co_name + + @property + def path(self) -> Path | str: + """Return a path object pointing to source code, or an ``str`` in + case of ``OSError`` / non-existing file.""" + if not self.raw.co_filename: + return "" + try: + p = absolutepath(self.raw.co_filename) + # maybe don't try this checking + if not p.exists(): + raise OSError("path check failed.") + return p + except OSError: + # XXX maybe try harder like the weird logic + # in the standard lib [linecache.updatecache] does? + return self.raw.co_filename + + @property + def fullsource(self) -> Source | None: + """Return a _pytest._code.Source object for the full source file of the code.""" + full, _ = findsource(self.raw) + return full + + def source(self) -> Source: + """Return a _pytest._code.Source object for the code object's source only.""" + # return source only for that part of code + return Source(self.raw) + + def getargs(self, var: bool = False) -> tuple[str, ...]: + """Return a tuple with the argument names for the code object. + + If 'var' is set True also return the names of the variable and + keyword arguments when present. + """ + # Handy shortcut for getting args. + raw = self.raw + argcount = raw.co_argcount + if var: + argcount += raw.co_flags & CO_VARARGS + argcount += raw.co_flags & CO_VARKEYWORDS + return raw.co_varnames[:argcount] + + +class Frame: + """Wrapper around a Python frame holding f_locals and f_globals + in which expressions can be evaluated.""" + + __slots__ = ("raw",) + + def __init__(self, frame: FrameType) -> None: + self.raw = frame + + @property + def lineno(self) -> int: + return self.raw.f_lineno - 1 + + @property + def f_globals(self) -> dict[str, Any]: + return self.raw.f_globals + + @property + def f_locals(self) -> dict[str, Any]: + return self.raw.f_locals + + @property + def code(self) -> Code: + return Code(self.raw.f_code) + + @property + def statement(self) -> Source: + """Statement this frame is at.""" + if self.code.fullsource is None: + return Source("") + return self.code.fullsource.getstatement(self.lineno) + + def eval(self, code, **vars): + """Evaluate 'code' in the frame. + + 'vars' are optional additional local variables. + + Returns the result of the evaluation. + """ + f_locals = self.f_locals.copy() + f_locals.update(vars) + return eval(code, self.f_globals, f_locals) + + def repr(self, object: object) -> str: + """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" + return saferepr(object) + + def getargs(self, var: bool = False): + """Return a list of tuples (name, value) for all arguments. + + If 'var' is set True, also include the variable and keyword arguments + when present. + """ + retval = [] + for arg in self.code.getargs(var): + try: + retval.append((arg, self.f_locals[arg])) + except KeyError: + pass # this can occur when using Psyco + return retval + + +class TracebackEntry: + """A single entry in a Traceback.""" + + __slots__ = ("_rawentry", "_repr_style") + + def __init__( + self, + rawentry: TracebackType, + repr_style: Literal["short", "long"] | None = None, + ) -> None: + self._rawentry: Final = rawentry + self._repr_style: Final = repr_style + + def with_repr_style( + self, repr_style: Literal["short", "long"] | None + ) -> TracebackEntry: + return TracebackEntry(self._rawentry, repr_style) + + @property + def lineno(self) -> int: + return self._rawentry.tb_lineno - 1 + + @property + def frame(self) -> Frame: + return Frame(self._rawentry.tb_frame) + + @property + def relline(self) -> int: + return self.lineno - self.frame.code.firstlineno + + def __repr__(self) -> str: + return "" % (self.frame.code.path, self.lineno + 1) + + @property + def statement(self) -> Source: + """_pytest._code.Source object for the current statement.""" + source = self.frame.code.fullsource + assert source is not None + return source.getstatement(self.lineno) + + @property + def path(self) -> Path | str: + """Path to the source code.""" + return self.frame.code.path + + @property + def locals(self) -> dict[str, Any]: + """Locals of underlying frame.""" + return self.frame.f_locals + + def getfirstlinesource(self) -> int: + return self.frame.code.firstlineno + + def getsource( + self, astcache: dict[str | Path, ast.AST] | None = None + ) -> Source | None: + """Return failing source code.""" + # we use the passed in astcache to not reparse asttrees + # within exception info printing + source = self.frame.code.fullsource + if source is None: + return None + key = astnode = None + if astcache is not None: + key = self.frame.code.path + if key is not None: + astnode = astcache.get(key, None) + start = self.getfirstlinesource() + try: + astnode, _, end = getstatementrange_ast( + self.lineno, source, astnode=astnode + ) + except SyntaxError: + end = self.lineno + 1 + else: + if key is not None and astcache is not None: + astcache[key] = astnode + return source[start:end] + + source = property(getsource) + + def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool: + """Return True if the current frame has a var __tracebackhide__ + resolving to True. + + If __tracebackhide__ is a callable, it gets called with the + ExceptionInfo instance and can decide whether to hide the traceback. + + Mostly for internal use. + """ + tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False + for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): + # in normal cases, f_locals and f_globals are dictionaries + # however via `exec(...)` / `eval(...)` they can be other types + # (even incorrect types!). + # as such, we suppress all exceptions while accessing __tracebackhide__ + try: + tbh = maybe_ns_dct["__tracebackhide__"] + except Exception: + pass + else: + break + if tbh and callable(tbh): + return tbh(excinfo) + return tbh + + def __str__(self) -> str: + name = self.frame.code.name + try: + line = str(self.statement).lstrip() + except KeyboardInterrupt: + raise + except BaseException: + line = "???" + # This output does not quite match Python's repr for traceback entries, + # but changing it to do so would break certain plugins. See + # https://github.com/pytest-dev/pytest/pull/7535/ for details. + return " File %r:%d in %s\n %s\n" % ( + str(self.path), + self.lineno + 1, + name, + line, + ) + + @property + def name(self) -> str: + """co_name of underlying code.""" + return self.frame.code.raw.co_name + + +class Traceback(List[TracebackEntry]): + """Traceback objects encapsulate and offer higher level access to Traceback entries.""" + + def __init__( + self, + tb: TracebackType | Iterable[TracebackEntry], + ) -> None: + """Initialize from given python traceback object and ExceptionInfo.""" + if isinstance(tb, TracebackType): + + def f(cur: TracebackType) -> Iterable[TracebackEntry]: + cur_: TracebackType | None = cur + while cur_ is not None: + yield TracebackEntry(cur_) + cur_ = cur_.tb_next + + super().__init__(f(tb)) + else: + super().__init__(tb) + + def cut( + self, + path: os.PathLike[str] | str | None = None, + lineno: int | None = None, + firstlineno: int | None = None, + excludepath: os.PathLike[str] | None = None, + ) -> Traceback: + """Return a Traceback instance wrapping part of this Traceback. + + By providing any combination of path, lineno and firstlineno, the + first frame to start the to-be-returned traceback is determined. + + This allows cutting the first part of a Traceback instance e.g. + for formatting reasons (removing some uninteresting bits that deal + with handling of the exception/traceback). + """ + path_ = None if path is None else os.fspath(path) + excludepath_ = None if excludepath is None else os.fspath(excludepath) + for x in self: + code = x.frame.code + codepath = code.path + if path is not None and str(codepath) != path_: + continue + if ( + excludepath is not None + and isinstance(codepath, Path) + and excludepath_ in (str(p) for p in codepath.parents) # type: ignore[operator] + ): + continue + if lineno is not None and x.lineno != lineno: + continue + if firstlineno is not None and x.frame.code.firstlineno != firstlineno: + continue + return Traceback(x._rawentry) + return self + + @overload + def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ... + + @overload + def __getitem__(self, key: slice) -> Traceback: ... + + def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback: + if isinstance(key, slice): + return self.__class__(super().__getitem__(key)) + else: + return super().__getitem__(key) + + def filter( + self, + excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool], + /, + ) -> Traceback: + """Return a Traceback instance with certain items removed. + + If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s + which are hidden (see ishidden() above). + + Otherwise, the filter is a function that gets a single argument, a + ``TracebackEntry`` instance, and should return True when the item should + be added to the ``Traceback``, False when not. + """ + if isinstance(excinfo_or_fn, ExceptionInfo): + fn = lambda x: not x.ishidden(excinfo_or_fn) # noqa: E731 + else: + fn = excinfo_or_fn + return Traceback(filter(fn, self)) + + def recursionindex(self) -> int | None: + """Return the index of the frame/TracebackEntry where recursion originates if + appropriate, None if no recursion occurred.""" + cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {} + for i, entry in enumerate(self): + # id for the code.raw is needed to work around + # the strange metaprogramming in the decorator lib from pypi + # which generates code objects that have hash/value equality + # XXX needs a test + key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno + values = cache.setdefault(key, []) + # Since Python 3.13 f_locals is a proxy, freeze it. + loc = dict(entry.frame.f_locals) + if values: + for otherloc in values: + if otherloc == loc: + return i + values.append(loc) + return None + + +E = TypeVar("E", bound=BaseException, covariant=True) + + +@final +@dataclasses.dataclass +class ExceptionInfo(Generic[E]): + """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" + + _assert_start_repr: ClassVar = "AssertionError('assert " + + _excinfo: tuple[type[E], E, TracebackType] | None + _striptext: str + _traceback: Traceback | None + + def __init__( + self, + excinfo: tuple[type[E], E, TracebackType] | None, + striptext: str = "", + traceback: Traceback | None = None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self._excinfo = excinfo + self._striptext = striptext + self._traceback = traceback + + @classmethod + def from_exception( + cls, + # Ignoring error: "Cannot use a covariant type variable as a parameter". + # This is OK to ignore because this class is (conceptually) readonly. + # See https://github.com/python/mypy/issues/7049. + exception: E, # type: ignore[misc] + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: + """Return an ExceptionInfo for an existing exception. + + The exception must have a non-``None`` ``__traceback__`` attribute, + otherwise this function fails with an assertion error. This means that + the exception must have been raised, or added a traceback with the + :py:meth:`~BaseException.with_traceback()` method. + + :param exprinfo: + A text string helping to determine if we should strip + ``AssertionError`` from the output. Defaults to the exception + message/``__str__()``. + + .. versionadded:: 7.4 + """ + assert exception.__traceback__, ( + "Exceptions passed to ExcInfo.from_exception(...)" + " must have a non-None __traceback__." + ) + exc_info = (type(exception), exception, exception.__traceback__) + return cls.from_exc_info(exc_info, exprinfo) + + @classmethod + def from_exc_info( + cls, + exc_info: tuple[type[E], E, TracebackType], + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: + """Like :func:`from_exception`, but using old-style exc_info tuple.""" + _striptext = "" + if exprinfo is None and isinstance(exc_info[1], AssertionError): + exprinfo = getattr(exc_info[1], "msg", None) + if exprinfo is None: + exprinfo = saferepr(exc_info[1]) + if exprinfo and exprinfo.startswith(cls._assert_start_repr): + _striptext = "AssertionError: " + + return cls(exc_info, _striptext, _ispytest=True) + + @classmethod + def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: + """Return an ExceptionInfo matching the current traceback. + + .. warning:: + + Experimental API + + :param exprinfo: + A text string helping to determine if we should strip + ``AssertionError`` from the output. Defaults to the exception + message/``__str__()``. + """ + tup = sys.exc_info() + assert tup[0] is not None, "no current exception" + assert tup[1] is not None, "no current exception" + assert tup[2] is not None, "no current exception" + exc_info = (tup[0], tup[1], tup[2]) + return ExceptionInfo.from_exc_info(exc_info, exprinfo) + + @classmethod + def for_later(cls) -> ExceptionInfo[E]: + """Return an unfilled ExceptionInfo.""" + return cls(None, _ispytest=True) + + def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None: + """Fill an unfilled ExceptionInfo created with ``for_later()``.""" + assert self._excinfo is None, "ExceptionInfo was already filled" + self._excinfo = exc_info + + @property + def type(self) -> type[E]: + """The exception class.""" + assert ( + self._excinfo is not None + ), ".type can only be used after the context manager exits" + return self._excinfo[0] + + @property + def value(self) -> E: + """The exception value.""" + assert ( + self._excinfo is not None + ), ".value can only be used after the context manager exits" + return self._excinfo[1] + + @property + def tb(self) -> TracebackType: + """The exception raw traceback.""" + assert ( + self._excinfo is not None + ), ".tb can only be used after the context manager exits" + return self._excinfo[2] + + @property + def typename(self) -> str: + """The type name of the exception.""" + assert ( + self._excinfo is not None + ), ".typename can only be used after the context manager exits" + return self.type.__name__ + + @property + def traceback(self) -> Traceback: + """The traceback.""" + if self._traceback is None: + self._traceback = Traceback(self.tb) + return self._traceback + + @traceback.setter + def traceback(self, value: Traceback) -> None: + self._traceback = value + + def __repr__(self) -> str: + if self._excinfo is None: + return "" + return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>" + + def exconly(self, tryshort: bool = False) -> str: + """Return the exception as a string. + + When 'tryshort' resolves to True, and the exception is an + AssertionError, only the actual exception part of the exception + representation is returned (so 'AssertionError: ' is removed from + the beginning). + """ + lines = format_exception_only(self.type, self.value) + text = "".join(lines) + text = text.rstrip() + if tryshort: + if text.startswith(self._striptext): + text = text[len(self._striptext) :] + return text + + def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: + """Return True if the exception is an instance of exc. + + Consider using ``isinstance(excinfo.value, exc)`` instead. + """ + return isinstance(self.value, exc) + + def _getreprcrash(self) -> ReprFileLocation | None: + # Find last non-hidden traceback entry that led to the exception of the + # traceback, or None if all hidden. + for i in range(-1, -len(self.traceback) - 1, -1): + entry = self.traceback[i] + if not entry.ishidden(self): + path, lineno = entry.frame.code.raw.co_filename, entry.lineno + exconly = self.exconly(tryshort=True) + return ReprFileLocation(path, lineno + 1, exconly) + return None + + def getrepr( + self, + showlocals: bool = False, + style: TracebackStyle = "long", + abspath: bool = False, + tbfilter: bool + | Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True, + funcargs: bool = False, + truncate_locals: bool = True, + truncate_args: bool = True, + chain: bool = True, + ) -> ReprExceptionInfo | ExceptionChainRepr: + """Return str()able representation of this exception info. + + :param bool showlocals: + Show locals per traceback entry. + Ignored if ``style=="native"``. + + :param str style: + long|short|line|no|native|value traceback style. + + :param bool abspath: + If paths should be changed to absolute or left unchanged. + + :param tbfilter: + A filter for traceback entries. + + * If false, don't hide any entries. + * If true, hide internal entries and entries that contain a local + variable ``__tracebackhide__ = True``. + * If a callable, delegates the filtering to the callable. + + Ignored if ``style`` is ``"native"``. + + :param bool funcargs: + Show fixtures ("funcargs" for legacy purposes) per traceback entry. + + :param bool truncate_locals: + With ``showlocals==True``, make sure locals can be safely represented as strings. + + :param bool truncate_args: + With ``showargs==True``, make sure args can be safely represented as strings. + + :param bool chain: + If chained exceptions in Python 3 should be shown. + + .. versionchanged:: 3.9 + + Added the ``chain`` parameter. + """ + if style == "native": + return ReprExceptionInfo( + reprtraceback=ReprTracebackNative( + traceback.format_exception( + self.type, + self.value, + self.traceback[0]._rawentry if self.traceback else None, + ) + ), + reprcrash=self._getreprcrash(), + ) + + fmt = FormattedExcinfo( + showlocals=showlocals, + style=style, + abspath=abspath, + tbfilter=tbfilter, + funcargs=funcargs, + truncate_locals=truncate_locals, + truncate_args=truncate_args, + chain=chain, + ) + return fmt.repr_excinfo(self) + + def _stringify_exception(self, exc: BaseException) -> str: + try: + notes = getattr(exc, "__notes__", []) + except KeyError: + # Workaround for https://github.com/python/cpython/issues/98778 on + # Python <= 3.9, and some 3.10 and 3.11 patch versions. + HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ()) + if sys.version_info < (3, 12) and isinstance(exc, HTTPError): + notes = [] + else: + raise + + return "\n".join( + [ + str(exc), + *notes, + ] + ) + + def match(self, regexp: str | Pattern[str]) -> Literal[True]: + """Check whether the regular expression `regexp` matches the string + representation of the exception using :func:`python:re.search`. + + If it matches `True` is returned, otherwise an `AssertionError` is raised. + """ + __tracebackhide__ = True + value = self._stringify_exception(self.value) + msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" + if regexp == value: + msg += "\n Did you mean to `re.escape()` the regex?" + assert re.search(regexp, value), msg + # Return True to allow for "assert excinfo.match()". + return True + + def _group_contains( + self, + exc_group: BaseExceptionGroup[BaseException], + expected_exception: EXCEPTION_OR_MORE, + match: str | Pattern[str] | None, + target_depth: int | None = None, + current_depth: int = 1, + ) -> bool: + """Return `True` if a `BaseExceptionGroup` contains a matching exception.""" + if (target_depth is not None) and (current_depth > target_depth): + # already descended past the target depth + return False + for exc in exc_group.exceptions: + if isinstance(exc, BaseExceptionGroup): + if self._group_contains( + exc, expected_exception, match, target_depth, current_depth + 1 + ): + return True + if (target_depth is not None) and (current_depth != target_depth): + # not at the target depth, no match + continue + if not isinstance(exc, expected_exception): + continue + if match is not None: + value = self._stringify_exception(exc) + if not re.search(match, value): + continue + return True + return False + + def group_contains( + self, + expected_exception: EXCEPTION_OR_MORE, + *, + match: str | Pattern[str] | None = None, + depth: int | None = None, + ) -> bool: + """Check whether a captured exception group contains a matching exception. + + :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception: + The expected exception type, or a tuple if one of multiple possible + exception types are expected. + + :param str | Pattern[str] | None match: + If specified, a string containing a regular expression, + or a regular expression object, that is tested against the string + representation of the exception and its `PEP-678 ` `__notes__` + using :func:`re.search`. + + To match a literal string that may contain :ref:`special characters + `, the pattern can first be escaped with :func:`re.escape`. + + :param Optional[int] depth: + If `None`, will search for a matching exception at any nesting depth. + If >= 1, will only match an exception if it's at the specified depth (depth = 1 being + the exceptions contained within the topmost exception group). + + .. versionadded:: 8.0 + """ + msg = "Captured exception is not an instance of `BaseExceptionGroup`" + assert isinstance(self.value, BaseExceptionGroup), msg + msg = "`depth` must be >= 1 if specified" + assert (depth is None) or (depth >= 1), msg + return self._group_contains(self.value, expected_exception, match, depth) + + +@dataclasses.dataclass +class FormattedExcinfo: + """Presenting information about failing Functions and Generators.""" + + # for traceback entries + flow_marker: ClassVar = ">" + fail_marker: ClassVar = "E" + + showlocals: bool = False + style: TracebackStyle = "long" + abspath: bool = True + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True + funcargs: bool = False + truncate_locals: bool = True + truncate_args: bool = True + chain: bool = True + astcache: dict[str | Path, ast.AST] = dataclasses.field( + default_factory=dict, init=False, repr=False + ) + + def _getindent(self, source: Source) -> int: + # Figure out indent for the given source. + try: + s = str(source.getstatement(len(source) - 1)) + except KeyboardInterrupt: + raise + except BaseException: + try: + s = str(source[-1]) + except KeyboardInterrupt: + raise + except BaseException: + return 0 + return 4 + (len(s) - len(s.lstrip())) + + def _getentrysource(self, entry: TracebackEntry) -> Source | None: + source = entry.getsource(self.astcache) + if source is not None: + source = source.deindent() + return source + + def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None: + if self.funcargs: + args = [] + for argname, argvalue in entry.frame.getargs(var=True): + if self.truncate_args: + str_repr = saferepr(argvalue) + else: + str_repr = saferepr(argvalue, maxsize=None) + args.append((argname, str_repr)) + return ReprFuncArgs(args) + return None + + def get_source( + self, + source: Source | None, + line_index: int = -1, + excinfo: ExceptionInfo[BaseException] | None = None, + short: bool = False, + ) -> list[str]: + """Return formatted and marked up source lines.""" + lines = [] + if source is not None and line_index < 0: + line_index += len(source) + if source is None or line_index >= len(source.lines) or line_index < 0: + # `line_index` could still be outside `range(len(source.lines))` if + # we're processing AST with pathological position attributes. + source = Source("???") + line_index = 0 + space_prefix = " " + if short: + lines.append(space_prefix + source.lines[line_index].strip()) + else: + for line in source.lines[:line_index]: + lines.append(space_prefix + line) + lines.append(self.flow_marker + " " + source.lines[line_index]) + for line in source.lines[line_index + 1 :]: + lines.append(space_prefix + line) + if excinfo is not None: + indent = 4 if short else self._getindent(source) + lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) + return lines + + def get_exconly( + self, + excinfo: ExceptionInfo[BaseException], + indent: int = 4, + markall: bool = False, + ) -> list[str]: + lines = [] + indentstr = " " * indent + # Get the real exception information out. + exlines = excinfo.exconly(tryshort=True).split("\n") + failindent = self.fail_marker + indentstr[1:] + for line in exlines: + lines.append(failindent + line) + if not markall: + failindent = indentstr + return lines + + def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None: + if self.showlocals: + lines = [] + keys = [loc for loc in locals if loc[0] != "@"] + keys.sort() + for name in keys: + value = locals[name] + if name == "__builtins__": + lines.append("__builtins__ = ") + else: + # This formatting could all be handled by the + # _repr() function, which is only reprlib.Repr in + # disguise, so is very configurable. + if self.truncate_locals: + str_repr = saferepr(value) + else: + str_repr = safeformat(value) + # if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)): + lines.append(f"{name:<10} = {str_repr}") + # else: + # self._line("%-10s =\\" % (name,)) + # # XXX + # pprint.pprint(value, stream=self.excinfowriter) + return ReprLocals(lines) + return None + + def repr_traceback_entry( + self, + entry: TracebackEntry | None, + excinfo: ExceptionInfo[BaseException] | None = None, + ) -> ReprEntry: + lines: list[str] = [] + style = ( + entry._repr_style + if entry is not None and entry._repr_style is not None + else self.style + ) + if style in ("short", "long") and entry is not None: + source = self._getentrysource(entry) + if source is None: + source = Source("???") + line_index = 0 + else: + line_index = entry.lineno - entry.getfirstlinesource() + short = style == "short" + reprargs = self.repr_args(entry) if not short else None + s = self.get_source(source, line_index, excinfo, short=short) + lines.extend(s) + if short: + message = f"in {entry.name}" + else: + message = excinfo and excinfo.typename or "" + entry_path = entry.path + path = self._makepath(entry_path) + reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) + localsrepr = self.repr_locals(entry.locals) + return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) + elif style == "value": + if excinfo: + lines.extend(str(excinfo.value).split("\n")) + return ReprEntry(lines, None, None, None, style) + else: + if excinfo: + lines.extend(self.get_exconly(excinfo, indent=4)) + return ReprEntry(lines, None, None, None, style) + + def _makepath(self, path: Path | str) -> str: + if not self.abspath and isinstance(path, Path): + try: + np = bestrelpath(Path.cwd(), path) + except OSError: + return str(path) + if len(np) < len(str(path)): + return np + return str(path) + + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback: + traceback = excinfo.traceback + if callable(self.tbfilter): + traceback = self.tbfilter(excinfo) + elif self.tbfilter: + traceback = traceback.filter(excinfo) + + if isinstance(excinfo.value, RecursionError): + traceback, extraline = self._truncate_recursive_traceback(traceback) + else: + extraline = None + + if not traceback: + if extraline is None: + extraline = "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames." + entries = [self.repr_traceback_entry(None, excinfo)] + return ReprTraceback(entries, extraline, style=self.style) + + last = traceback[-1] + if self.style == "value": + entries = [self.repr_traceback_entry(last, excinfo)] + return ReprTraceback(entries, None, style=self.style) + + entries = [ + self.repr_traceback_entry(entry, excinfo if last == entry else None) + for entry in traceback + ] + return ReprTraceback(entries, extraline, style=self.style) + + def _truncate_recursive_traceback( + self, traceback: Traceback + ) -> tuple[Traceback, str | None]: + """Truncate the given recursive traceback trying to find the starting + point of the recursion. + + The detection is done by going through each traceback entry and + finding the point in which the locals of the frame are equal to the + locals of a previous frame (see ``recursionindex()``). + + Handle the situation where the recursion process might raise an + exception (for example comparing numpy arrays using equality raises a + TypeError), in which case we do our best to warn the user of the + error and show a limited traceback. + """ + try: + recursionindex = traceback.recursionindex() + except Exception as e: + max_frames = 10 + extraline: str | None = ( + "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" + " The following exception happened when comparing locals in the stack frame:\n" + f" {type(e).__name__}: {e!s}\n" + f" Displaying first and last {max_frames} stack frames out of {len(traceback)}." + ) + # Type ignored because adding two instances of a List subtype + # currently incorrectly has type List instead of the subtype. + traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore + else: + if recursionindex is not None: + extraline = "!!! Recursion detected (same locals & position)" + traceback = traceback[: recursionindex + 1] + else: + extraline = None + + return traceback, extraline + + def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr: + repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = [] + e: BaseException | None = excinfo.value + excinfo_: ExceptionInfo[BaseException] | None = excinfo + descr = None + seen: set[int] = set() + while e is not None and id(e) not in seen: + seen.add(id(e)) + + if excinfo_: + # Fall back to native traceback as a temporary workaround until + # full support for exception groups added to ExceptionInfo. + # See https://github.com/pytest-dev/pytest/issues/9159 + if isinstance(e, BaseExceptionGroup): + reprtraceback: ReprTracebackNative | ReprTraceback = ( + ReprTracebackNative( + traceback.format_exception( + type(excinfo_.value), + excinfo_.value, + excinfo_.traceback[0]._rawentry, + ) + ) + ) + else: + reprtraceback = self.repr_traceback(excinfo_) + reprcrash = excinfo_._getreprcrash() + else: + # Fallback to native repr if the exception doesn't have a traceback: + # ExceptionInfo objects require a full traceback to work. + reprtraceback = ReprTracebackNative( + traceback.format_exception(type(e), e, None) + ) + reprcrash = None + repr_chain += [(reprtraceback, reprcrash, descr)] + + if e.__cause__ is not None and self.chain: + e = e.__cause__ + excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None + descr = "The above exception was the direct cause of the following exception:" + elif ( + e.__context__ is not None and not e.__suppress_context__ and self.chain + ): + e = e.__context__ + excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None + descr = "During handling of the above exception, another exception occurred:" + else: + e = None + repr_chain.reverse() + return ExceptionChainRepr(repr_chain) + + +@dataclasses.dataclass(eq=False) +class TerminalRepr: + def __str__(self) -> str: + # FYI this is called from pytest-xdist's serialization of exception + # information. + io = StringIO() + tw = TerminalWriter(file=io) + self.toterminal(tw) + return io.getvalue().strip() + + def __repr__(self) -> str: + return f"<{self.__class__} instance at {id(self):0x}>" + + def toterminal(self, tw: TerminalWriter) -> None: + raise NotImplementedError() + + +# This class is abstract -- only subclasses are instantiated. +@dataclasses.dataclass(eq=False) +class ExceptionRepr(TerminalRepr): + # Provided by subclasses. + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None + sections: list[tuple[str, str, str]] = dataclasses.field( + init=False, default_factory=list + ) + + def addsection(self, name: str, content: str, sep: str = "-") -> None: + self.sections.append((name, content, sep)) + + def toterminal(self, tw: TerminalWriter) -> None: + for name, content, sep in self.sections: + tw.sep(sep, name) + tw.line(content) + + +@dataclasses.dataclass(eq=False) +class ExceptionChainRepr(ExceptionRepr): + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]] + + def __init__( + self, + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]], + ) -> None: + # reprcrash and reprtraceback of the outermost (the newest) exception + # in the chain. + super().__init__( + reprtraceback=chain[-1][0], + reprcrash=chain[-1][1], + ) + self.chain = chain + + def toterminal(self, tw: TerminalWriter) -> None: + for element in self.chain: + element[0].toterminal(tw) + if element[2] is not None: + tw.line("") + tw.line(element[2], yellow=True) + super().toterminal(tw) + + +@dataclasses.dataclass(eq=False) +class ReprExceptionInfo(ExceptionRepr): + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None + + def toterminal(self, tw: TerminalWriter) -> None: + self.reprtraceback.toterminal(tw) + super().toterminal(tw) + + +@dataclasses.dataclass(eq=False) +class ReprTraceback(TerminalRepr): + reprentries: Sequence[ReprEntry | ReprEntryNative] + extraline: str | None + style: TracebackStyle + + entrysep: ClassVar = "_ " + + def toterminal(self, tw: TerminalWriter) -> None: + # The entries might have different styles. + for i, entry in enumerate(self.reprentries): + if entry.style == "long": + tw.line("") + entry.toterminal(tw) + if i < len(self.reprentries) - 1: + next_entry = self.reprentries[i + 1] + if ( + entry.style == "long" + or entry.style == "short" + and next_entry.style == "long" + ): + tw.sep(self.entrysep) + + if self.extraline: + tw.line(self.extraline) + + +class ReprTracebackNative(ReprTraceback): + def __init__(self, tblines: Sequence[str]) -> None: + self.reprentries = [ReprEntryNative(tblines)] + self.extraline = None + self.style = "native" + + +@dataclasses.dataclass(eq=False) +class ReprEntryNative(TerminalRepr): + lines: Sequence[str] + + style: ClassVar[TracebackStyle] = "native" + + def toterminal(self, tw: TerminalWriter) -> None: + tw.write("".join(self.lines)) + + +@dataclasses.dataclass(eq=False) +class ReprEntry(TerminalRepr): + lines: Sequence[str] + reprfuncargs: ReprFuncArgs | None + reprlocals: ReprLocals | None + reprfileloc: ReprFileLocation | None + style: TracebackStyle + + def _write_entry_lines(self, tw: TerminalWriter) -> None: + """Write the source code portions of a list of traceback entries with syntax highlighting. + + Usually entries are lines like these: + + " x = 1" + "> assert x == 2" + "E assert 1 == 2" + + This function takes care of rendering the "source" portions of it (the lines without + the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" + character, as doing so might break line continuations. + """ + if not self.lines: + return + + if self.style == "value": + # Using tw.write instead of tw.line for testing purposes due to TWMock implementation; + # lines written with TWMock.line and TWMock._write_source cannot be distinguished + # from each other, whereas lines written with TWMock.write are marked with TWMock.WRITE + for line in self.lines: + tw.write(line) + tw.write("\n") + return + + # separate indents and source lines that are not failures: we want to + # highlight the code but not the indentation, which may contain markers + # such as "> assert 0" + fail_marker = f"{FormattedExcinfo.fail_marker} " + indent_size = len(fail_marker) + indents: list[str] = [] + source_lines: list[str] = [] + failure_lines: list[str] = [] + for index, line in enumerate(self.lines): + is_failure_line = line.startswith(fail_marker) + if is_failure_line: + # from this point on all lines are considered part of the failure + failure_lines.extend(self.lines[index:]) + break + else: + indents.append(line[:indent_size]) + source_lines.append(line[indent_size:]) + + tw._write_source(source_lines, indents) + + # failure lines are always completely red and bold + for line in failure_lines: + tw.line(line, bold=True, red=True) + + def toterminal(self, tw: TerminalWriter) -> None: + if self.style == "short": + if self.reprfileloc: + self.reprfileloc.toterminal(tw) + self._write_entry_lines(tw) + if self.reprlocals: + self.reprlocals.toterminal(tw, indent=" " * 8) + return + + if self.reprfuncargs: + self.reprfuncargs.toterminal(tw) + + self._write_entry_lines(tw) + + if self.reprlocals: + tw.line("") + self.reprlocals.toterminal(tw) + if self.reprfileloc: + if self.lines: + tw.line("") + self.reprfileloc.toterminal(tw) + + def __str__(self) -> str: + return "{}\n{}\n{}".format( + "\n".join(self.lines), self.reprlocals, self.reprfileloc + ) + + +@dataclasses.dataclass(eq=False) +class ReprFileLocation(TerminalRepr): + path: str + lineno: int + message: str + + def __post_init__(self) -> None: + self.path = str(self.path) + + def toterminal(self, tw: TerminalWriter) -> None: + # Filename and lineno output for each entry, using an output format + # that most editors understand. + msg = self.message + i = msg.find("\n") + if i != -1: + msg = msg[:i] + tw.write(self.path, bold=True, red=True) + tw.line(f":{self.lineno}: {msg}") + + +@dataclasses.dataclass(eq=False) +class ReprLocals(TerminalRepr): + lines: Sequence[str] + + def toterminal(self, tw: TerminalWriter, indent="") -> None: + for line in self.lines: + tw.line(indent + line) + + +@dataclasses.dataclass(eq=False) +class ReprFuncArgs(TerminalRepr): + args: Sequence[tuple[str, object]] + + def toterminal(self, tw: TerminalWriter) -> None: + if self.args: + linesofar = "" + for name, value in self.args: + ns = f"{name} = {value}" + if len(ns) + len(linesofar) + 2 > tw.fullwidth: + if linesofar: + tw.line(linesofar) + linesofar = ns + else: + if linesofar: + linesofar += ", " + ns + else: + linesofar = ns + if linesofar: + tw.line(linesofar) + tw.line("") + + +def getfslineno(obj: object) -> tuple[str | Path, int]: + """Return source location (path, lineno) for the given object. + + If the source cannot be determined return ("", -1). + + The line number is 0-based. + """ + # xxx let decorators etc specify a sane ordering + # NOTE: this used to be done in _pytest.compat.getfslineno, initially added + # in 6ec13a2b9. It ("place_as") appears to be something very custom. + obj = get_real_func(obj) + if hasattr(obj, "place_as"): + obj = obj.place_as + + try: + code = Code.from_function(obj) + except TypeError: + try: + fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] + except TypeError: + return "", -1 + + fspath = fn and absolutepath(fn) or "" + lineno = -1 + if fspath: + try: + _, lineno = findsource(obj) + except OSError: + pass + return fspath, lineno + + return code.path, code.firstlineno + + +# Relative paths that we use to filter traceback entries from appearing to the user; +# see filter_traceback. +# note: if we need to add more paths than what we have now we should probably use a list +# for better maintenance. + +_PLUGGY_DIR = Path(pluggy.__file__.rstrip("oc")) +# pluggy is either a package or a single module depending on the version +if _PLUGGY_DIR.name == "__init__.py": + _PLUGGY_DIR = _PLUGGY_DIR.parent +_PYTEST_DIR = Path(_pytest.__file__).parent + + +def filter_traceback(entry: TracebackEntry) -> bool: + """Return True if a TracebackEntry instance should be included in tracebacks. + + We hide traceback entries of: + + * dynamically generated code (no code to show up for it); + * internal traceback from pytest or its internal libraries, py and pluggy. + """ + # entry.path might sometimes return a str object when the entry + # points to dynamically generated code. + # See https://bitbucket.org/pytest-dev/py/issues/71. + raw_filename = entry.frame.code.raw.co_filename + is_generated = "<" in raw_filename and ">" in raw_filename + if is_generated: + return False + + # entry.path might point to a non-existing file, in which case it will + # also return a str object. See #1133. + p = Path(entry.path) + + parents = p.parents + if _PLUGGY_DIR in parents: + return False + if _PYTEST_DIR in parents: + return False + + return True diff --git a/.venv/Lib/site-packages/_pytest/_code/source.py b/.venv/Lib/site-packages/_pytest/_code/source.py new file mode 100644 index 0000000..604aff8 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_code/source.py @@ -0,0 +1,215 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import ast +from bisect import bisect_right +import inspect +import textwrap +import tokenize +import types +from typing import Iterable +from typing import Iterator +from typing import overload +import warnings + + +class Source: + """An immutable object holding a source code fragment. + + When using Source(...), the source lines are deindented. + """ + + def __init__(self, obj: object = None) -> None: + if not obj: + self.lines: list[str] = [] + elif isinstance(obj, Source): + self.lines = obj.lines + elif isinstance(obj, (tuple, list)): + self.lines = deindent(x.rstrip("\n") for x in obj) + elif isinstance(obj, str): + self.lines = deindent(obj.split("\n")) + else: + try: + rawcode = getrawcode(obj) + src = inspect.getsource(rawcode) + except TypeError: + src = inspect.getsource(obj) # type: ignore[arg-type] + self.lines = deindent(src.split("\n")) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Source): + return NotImplemented + return self.lines == other.lines + + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore + + @overload + def __getitem__(self, key: int) -> str: ... + + @overload + def __getitem__(self, key: slice) -> Source: ... + + def __getitem__(self, key: int | slice) -> str | Source: + if isinstance(key, int): + return self.lines[key] + else: + if key.step not in (None, 1): + raise IndexError("cannot slice a Source with a step") + newsource = Source() + newsource.lines = self.lines[key.start : key.stop] + return newsource + + def __iter__(self) -> Iterator[str]: + return iter(self.lines) + + def __len__(self) -> int: + return len(self.lines) + + def strip(self) -> Source: + """Return new Source object with trailing and leading blank lines removed.""" + start, end = 0, len(self) + while start < end and not self.lines[start].strip(): + start += 1 + while end > start and not self.lines[end - 1].strip(): + end -= 1 + source = Source() + source.lines[:] = self.lines[start:end] + return source + + def indent(self, indent: str = " " * 4) -> Source: + """Return a copy of the source object with all lines indented by the + given indent-string.""" + newsource = Source() + newsource.lines = [(indent + line) for line in self.lines] + return newsource + + def getstatement(self, lineno: int) -> Source: + """Return Source statement which contains the given linenumber + (counted from 0).""" + start, end = self.getstatementrange(lineno) + return self[start:end] + + def getstatementrange(self, lineno: int) -> tuple[int, int]: + """Return (start, end) tuple which spans the minimal statement region + which containing the given lineno.""" + if not (0 <= lineno < len(self)): + raise IndexError("lineno out of range") + ast, start, end = getstatementrange_ast(lineno, self) + return start, end + + def deindent(self) -> Source: + """Return a new Source object deindented.""" + newsource = Source() + newsource.lines[:] = deindent(self.lines) + return newsource + + def __str__(self) -> str: + return "\n".join(self.lines) + + +# +# helper functions +# + + +def findsource(obj) -> tuple[Source | None, int]: + try: + sourcelines, lineno = inspect.findsource(obj) + except Exception: + return None, -1 + source = Source() + source.lines = [line.rstrip() for line in sourcelines] + return source, lineno + + +def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: + """Return code object for given function.""" + try: + return obj.__code__ # type: ignore[attr-defined,no-any-return] + except AttributeError: + pass + if trycall: + call = getattr(obj, "__call__", None) + if call and not isinstance(obj, type): + return getrawcode(call, trycall=False) + raise TypeError(f"could not get code object for {obj!r}") + + +def deindent(lines: Iterable[str]) -> list[str]: + return textwrap.dedent("\n".join(lines)).splitlines() + + +def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]: + # Flatten all statements and except handlers into one lineno-list. + # AST's line numbers start indexing at 1. + values: list[int] = [] + for x in ast.walk(node): + if isinstance(x, (ast.stmt, ast.ExceptHandler)): + # The lineno points to the class/def, so need to include the decorators. + if isinstance(x, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + for d in x.decorator_list: + values.append(d.lineno - 1) + values.append(x.lineno - 1) + for name in ("finalbody", "orelse"): + val: list[ast.stmt] | None = getattr(x, name, None) + if val: + # Treat the finally/orelse part as its own statement. + values.append(val[0].lineno - 1 - 1) + values.sort() + insert_index = bisect_right(values, lineno) + start = values[insert_index - 1] + if insert_index >= len(values): + end = None + else: + end = values[insert_index] + return start, end + + +def getstatementrange_ast( + lineno: int, + source: Source, + assertion: bool = False, + astnode: ast.AST | None = None, +) -> tuple[ast.AST, int, int]: + if astnode is None: + content = str(source) + # See #4260: + # Don't produce duplicate warnings when compiling source to find AST. + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + astnode = ast.parse(content, "source", "exec") + + start, end = get_statement_startend2(lineno, astnode) + # We need to correct the end: + # - ast-parsing strips comments + # - there might be empty lines + # - we might have lesser indented code blocks at the end + if end is None: + end = len(source.lines) + + if end > start + 1: + # Make sure we don't span differently indented code blocks + # by using the BlockFinder helper used which inspect.getsource() uses itself. + block_finder = inspect.BlockFinder() + # If we start with an indented line, put blockfinder to "started" mode. + block_finder.started = ( + bool(source.lines[start]) and source.lines[start][0].isspace() + ) + it = ((x + "\n") for x in source.lines[start:end]) + try: + for tok in tokenize.generate_tokens(lambda: next(it)): + block_finder.tokeneater(*tok) + except (inspect.EndOfBlock, IndentationError): + end = block_finder.last + start + except Exception: + pass + + # The end might still point to a comment or empty line, correct it. + while end: + line = source.lines[end - 1].lstrip() + if line.startswith("#") or not line: + end -= 1 + else: + break + return astnode, start, end diff --git a/.venv/Lib/site-packages/_pytest/_io/__init__.py b/.venv/Lib/site-packages/_pytest/_io/__init__.py new file mode 100644 index 0000000..b0155b1 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_io/__init__.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from .terminalwriter import get_terminal_width +from .terminalwriter import TerminalWriter + + +__all__ = [ + "TerminalWriter", + "get_terminal_width", +] diff --git a/.venv/Lib/site-packages/_pytest/_io/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/_io/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1bce9b3b0e6da0aefaa46d6fd1d1ac813db4ebd9 GIT binary patch literal 400 zcmXw!&q_i;6vk)n9~DIxLC_|+v@=LsO9W9!L4;rtTm}x4chb>&@5s!kxa=u%8R?7tIt=AfS;Q5_jcI9jZU7w;TEy{lD|5B@T4~VeMnPs~55|!1KgTv;fd9dRc9kpKa+J+(fb>0FPsUcd|y0{Hn_q zdoV&9*5wJi5@ux8m~~|&f{s>0(X)oNxv>|Wn_y~!cFfI)UGv^Z=-^5X6>CgII%T)= zi5Yy@$rwkxFARh6+zMlxAXO7UrqTj5#`COnmZ)eFi*sGY{rR8GA-0uEDP54GAJSNm V<5h|H`(NI{%6BWyOHo&l_yb#>aWen_ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/_io/__pycache__/pprint.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/_io/__pycache__/pprint.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbbe41a53b5569d22ea6ce4551de2f0ac86ea424 GIT binary patch literal 24386 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 q`T1qTaLh!an6!(f>xB?=VWB(TvIaW9T literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/_io/__pycache__/saferepr.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/_io/__pycache__/saferepr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb302f62f6f739c1187c9df8e7d0cf42a12f775a GIT binary patch literal 5846 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 T`Cr|>B{$T6xuKr!63h7yFL`Ig literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c17da843033b4cc981968f9dcf116845a6bed0b GIT binary patch literal 11307 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 TJXfD1Na2ebOrJ6V8_E9#L|1lV literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8c50510fe539b989762e59536f2d004f9e41c3f GIT binary patch literal 1706 zcmbVML2nyH6rR~#du^{bb`q+lMWvHO3NcDt3N44KLW&ZifVY4^DQ!il|N=R@rgc>f(B?jnjPc2NbdU@U3HRVwO=8qun&t!N5jD%Bp~ zo)y(;?56ghf7VKjCTRSowi2(axnxJ~=5T0Op63Tv;P{>ZHtF(O(YEXoyV{|kVfT-U z&XCT)#QO+#@1o#P)Ma)7XGcTIIgFGHa8@7+&(^F z8Zk%qbSTo#z!PS-zFS}c;}yqaq~vdsisjXb?Yp&#Cy2!v@ff3wlE5d_5mnc!6N}g- zi(59B5yu0zXK%UrREofOj|DXj*0bk{vr2*zJFYJXWQtU|;|10Ym*od6UY#N5iONrLdjI?gWJZQG2FiX*dSj`0sIT| z&>&AU*=I479*6bU%<0|C=E@uCL3ybQ_T`tpBtfC!+1*ZyPq_myoQYTkjy z@O*Ffumk>4;88_~>ea=0Za_Ram5;y@COw~5EZ5m$QO2QYu?S;PIq(xwLD{s0d`Kup z!cWSb1nl$_2(WpWVjiH0paRUH=37t9v0Zaw$DFu#@&5P^=1dDeF<)q3zq8U(_6)O) zZ@=;5=!w0A*&exN+=_LRXlNEJ)5Far4lfQ**SlDS_`*1kx;|8)u|aSKrtdpc$@(HEsE~x2XndN)0)fP None: + """Handle pretty printing operations onto a stream using a set of + configured parameters. + + indent + Number of spaces to indent for each level of nesting. + + width + Attempted maximum number of columns in the output. + + depth + The maximum depth to print out nested structures. + + """ + if indent < 0: + raise ValueError("indent must be >= 0") + if depth is not None and depth <= 0: + raise ValueError("depth must be > 0") + if not width: + raise ValueError("width must be != 0") + self._depth = depth + self._indent_per_level = indent + self._width = width + + def pformat(self, object: Any) -> str: + sio = _StringIO() + self._format(object, sio, 0, 0, set(), 0) + return sio.getvalue() + + def _format( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + objid = id(object) + if objid in context: + stream.write(_recursion(object)) + return + + p = self._dispatch.get(type(object).__repr__, None) + if p is not None: + context.add(objid) + p(self, object, stream, indent, allowance, context, level + 1) + context.remove(objid) + elif ( + _dataclasses.is_dataclass(object) # type:ignore[unreachable] + and not isinstance(object, type) + and object.__dataclass_params__.repr + and + # Check dataclass has generated repr method. + hasattr(object.__repr__, "__wrapped__") + and "__create_fn__" in object.__repr__.__wrapped__.__qualname__ + ): + context.add(objid) # type:ignore[unreachable] + self._pprint_dataclass( + object, stream, indent, allowance, context, level + 1 + ) + context.remove(objid) + else: + stream.write(self._repr(object, context, level)) + + def _pprint_dataclass( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + cls_name = object.__class__.__name__ + items = [ + (f.name, getattr(object, f.name)) + for f in _dataclasses.fields(object) + if f.repr + ] + stream.write(cls_name + "(") + self._format_namespace_items(items, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch: dict[ + Callable[..., str], + Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None], + ] = {} + + def _pprint_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + write = stream.write + write("{") + items = sorted(object.items(), key=_safe_tuple) + self._format_dict_items(items, stream, indent, allowance, context, level) + write("}") + + _dispatch[dict.__repr__] = _pprint_dict + + def _pprint_ordered_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + if not len(object): + stream.write(repr(object)) + return + cls = object.__class__ + stream.write(cls.__name__ + "(") + self._pprint_dict(object, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict + + def _pprint_list( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + stream.write("[") + self._format_items(object, stream, indent, allowance, context, level) + stream.write("]") + + _dispatch[list.__repr__] = _pprint_list + + def _pprint_tuple( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + stream.write("(") + self._format_items(object, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[tuple.__repr__] = _pprint_tuple + + def _pprint_set( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + if not len(object): + stream.write(repr(object)) + return + typ = object.__class__ + if typ is set: + stream.write("{") + endchar = "}" + else: + stream.write(typ.__name__ + "({") + endchar = "})" + object = sorted(object, key=_safe_key) + self._format_items(object, stream, indent, allowance, context, level) + stream.write(endchar) + + _dispatch[set.__repr__] = _pprint_set + _dispatch[frozenset.__repr__] = _pprint_set + + def _pprint_str( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + write = stream.write + if not len(object): + write(repr(object)) + return + chunks = [] + lines = object.splitlines(True) + if level == 1: + indent += 1 + allowance += 1 + max_width1 = max_width = self._width - indent + for i, line in enumerate(lines): + rep = repr(line) + if i == len(lines) - 1: + max_width1 -= allowance + if len(rep) <= max_width1: + chunks.append(rep) + else: + # A list of alternating (non-space, space) strings + parts = re.findall(r"\S*\s*", line) + assert parts + assert not parts[-1] + parts.pop() # drop empty last part + max_width2 = max_width + current = "" + for j, part in enumerate(parts): + candidate = current + part + if j == len(parts) - 1 and i == len(lines) - 1: + max_width2 -= allowance + if len(repr(candidate)) > max_width2: + if current: + chunks.append(repr(current)) + current = part + else: + current = candidate + if current: + chunks.append(repr(current)) + if len(chunks) == 1: + write(rep) + return + if level == 1: + write("(") + for i, rep in enumerate(chunks): + if i > 0: + write("\n" + " " * indent) + write(rep) + if level == 1: + write(")") + + _dispatch[str.__repr__] = _pprint_str + + def _pprint_bytes( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + write = stream.write + if len(object) <= 4: + write(repr(object)) + return + parens = level == 1 + if parens: + indent += 1 + allowance += 1 + write("(") + delim = "" + for rep in _wrap_bytes_repr(object, self._width - indent, allowance): + write(delim) + write(rep) + if not delim: + delim = "\n" + " " * indent + if parens: + write(")") + + _dispatch[bytes.__repr__] = _pprint_bytes + + def _pprint_bytearray( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + write = stream.write + write("bytearray(") + self._pprint_bytes( + bytes(object), stream, indent + 10, allowance + 1, context, level + 1 + ) + write(")") + + _dispatch[bytearray.__repr__] = _pprint_bytearray + + def _pprint_mappingproxy( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + stream.write("mappingproxy(") + self._format(object.copy(), stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy + + def _pprint_simplenamespace( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + if type(object) is _types.SimpleNamespace: + # The SimpleNamespace repr is "namespace" instead of the class + # name, so we do the same here. For subclasses; use the class name. + cls_name = "namespace" + else: + cls_name = object.__class__.__name__ + items = object.__dict__.items() + stream.write(cls_name + "(") + self._format_namespace_items(items, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace + + def _format_dict_items( + self, + items: list[tuple[Any, Any]], + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + if not items: + return + + write = stream.write + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + for key, ent in items: + write(delimnl) + write(self._repr(key, context, level)) + write(": ") + self._format(ent, stream, item_indent, 1, context, level) + write(",") + + write("\n" + " " * indent) + + def _format_namespace_items( + self, + items: list[tuple[Any, Any]], + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + if not items: + return + + write = stream.write + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + for key, ent in items: + write(delimnl) + write(key) + write("=") + if id(ent) in context: + # Special-case representation of recursion to match standard + # recursive dataclass repr. + write("...") + else: + self._format( + ent, + stream, + item_indent + len(key) + 1, + 1, + context, + level, + ) + + write(",") + + write("\n" + " " * indent) + + def _format_items( + self, + items: list[Any], + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + if not items: + return + + write = stream.write + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + + for item in items: + write(delimnl) + self._format(item, stream, item_indent, 1, context, level) + write(",") + + write("\n" + " " * indent) + + def _repr(self, object: Any, context: set[int], level: int) -> str: + return self._safe_repr(object, context.copy(), self._depth, level) + + def _pprint_default_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + rdf = self._repr(object.default_factory, context, level) + stream.write(f"{object.__class__.__name__}({rdf}, ") + self._pprint_dict(object, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict + + def _pprint_counter( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + stream.write(object.__class__.__name__ + "(") + + if object: + stream.write("{") + items = object.most_common() + self._format_dict_items(items, stream, indent, allowance, context, level) + stream.write("}") + + stream.write(")") + + _dispatch[_collections.Counter.__repr__] = _pprint_counter + + def _pprint_chain_map( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): + stream.write(repr(object)) + return + + stream.write(object.__class__.__name__ + "(") + self._format_items(object.maps, stream, indent, allowance, context, level) + stream.write(")") + + _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map + + def _pprint_deque( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + stream.write(object.__class__.__name__ + "(") + if object.maxlen is not None: + stream.write("maxlen=%d, " % object.maxlen) + stream.write("[") + + self._format_items(object, stream, indent, allowance + 1, context, level) + stream.write("])") + + _dispatch[_collections.deque.__repr__] = _pprint_deque + + def _pprint_user_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + self._format(object.data, stream, indent, allowance, context, level - 1) + + _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict + + def _pprint_user_list( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + self._format(object.data, stream, indent, allowance, context, level - 1) + + _dispatch[_collections.UserList.__repr__] = _pprint_user_list + + def _pprint_user_string( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: set[int], + level: int, + ) -> None: + self._format(object.data, stream, indent, allowance, context, level - 1) + + _dispatch[_collections.UserString.__repr__] = _pprint_user_string + + def _safe_repr( + self, object: Any, context: set[int], maxlevels: int | None, level: int + ) -> str: + typ = type(object) + if typ in _builtin_scalars: + return repr(object) + + r = getattr(typ, "__repr__", None) + + if issubclass(typ, dict) and r is dict.__repr__: + if not object: + return "{}" + objid = id(object) + if maxlevels and level >= maxlevels: + return "{...}" + if objid in context: + return _recursion(object) + context.add(objid) + components: list[str] = [] + append = components.append + level += 1 + for k, v in sorted(object.items(), key=_safe_tuple): + krepr = self._safe_repr(k, context, maxlevels, level) + vrepr = self._safe_repr(v, context, maxlevels, level) + append(f"{krepr}: {vrepr}") + context.remove(objid) + return "{{{}}}".format(", ".join(components)) + + if (issubclass(typ, list) and r is list.__repr__) or ( + issubclass(typ, tuple) and r is tuple.__repr__ + ): + if issubclass(typ, list): + if not object: + return "[]" + format = "[%s]" + elif len(object) == 1: + format = "(%s,)" + else: + if not object: + return "()" + format = "(%s)" + objid = id(object) + if maxlevels and level >= maxlevels: + return format % "..." + if objid in context: + return _recursion(object) + context.add(objid) + components = [] + append = components.append + level += 1 + for o in object: + orepr = self._safe_repr(o, context, maxlevels, level) + append(orepr) + context.remove(objid) + return format % ", ".join(components) + + return repr(object) + + +_builtin_scalars = frozenset( + {str, bytes, bytearray, float, complex, bool, type(None), int} +) + + +def _recursion(object: Any) -> str: + return f"" + + +def _wrap_bytes_repr(object: Any, width: int, allowance: int) -> Iterator[str]: + current = b"" + last = len(object) // 4 * 4 + for i in range(0, len(object), 4): + part = object[i : i + 4] + candidate = current + part + if i == last: + width -= allowance + if len(repr(candidate)) > width: + if current: + yield repr(current) + current = part + else: + current = candidate + if current: + yield repr(current) diff --git a/.venv/Lib/site-packages/_pytest/_io/saferepr.py b/.venv/Lib/site-packages/_pytest/_io/saferepr.py new file mode 100644 index 0000000..cee70e3 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_io/saferepr.py @@ -0,0 +1,130 @@ +from __future__ import annotations + +import pprint +import reprlib + + +def _try_repr_or_str(obj: object) -> str: + try: + return repr(obj) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException: + return f'{type(obj).__name__}("{obj}")' + + +def _format_repr_exception(exc: BaseException, obj: object) -> str: + try: + exc_info = _try_repr_or_str(exc) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as inner_exc: + exc_info = f"unpresentable exception ({_try_repr_or_str(inner_exc)})" + return ( + f"<[{exc_info} raised in repr()] {type(obj).__name__} object at 0x{id(obj):x}>" + ) + + +def _ellipsize(s: str, maxsize: int) -> str: + if len(s) > maxsize: + i = max(0, (maxsize - 3) // 2) + j = max(0, maxsize - 3 - i) + return s[:i] + "..." + s[len(s) - j :] + return s + + +class SafeRepr(reprlib.Repr): + """ + repr.Repr that limits the resulting size of repr() and includes + information on exceptions raised during the call. + """ + + def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None: + """ + :param maxsize: + If not None, will truncate the resulting repr to that specific size, using ellipsis + somewhere in the middle to hide the extra text. + If None, will not impose any size limits on the returning repr. + """ + super().__init__() + # ``maxstring`` is used by the superclass, and needs to be an int; using a + # very large number in case maxsize is None, meaning we want to disable + # truncation. + self.maxstring = maxsize if maxsize is not None else 1_000_000_000 + self.maxsize = maxsize + self.use_ascii = use_ascii + + def repr(self, x: object) -> str: + try: + if self.use_ascii: + s = ascii(x) + else: + s = super().repr(x) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as exc: + s = _format_repr_exception(exc, x) + if self.maxsize is not None: + s = _ellipsize(s, self.maxsize) + return s + + def repr_instance(self, x: object, level: int) -> str: + try: + s = repr(x) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as exc: + s = _format_repr_exception(exc, x) + if self.maxsize is not None: + s = _ellipsize(s, self.maxsize) + return s + + +def safeformat(obj: object) -> str: + """Return a pretty printed string for the given object. + + Failing __repr__ functions of user instances will be represented + with a short exception info. + """ + try: + return pprint.pformat(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) + + +# Maximum size of overall repr of objects to display during assertion errors. +DEFAULT_REPR_MAX_SIZE = 240 + + +def saferepr( + obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False +) -> str: + """Return a size-limited safe repr-string for the given object. + + Failing __repr__ functions of user instances will be represented + with a short exception info and 'saferepr' generally takes + care to never raise exceptions itself. + + This function is a wrapper around the Repr/reprlib functionality of the + stdlib. + """ + return SafeRepr(maxsize, use_ascii).repr(obj) + + +def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str: + """Return an unlimited-size safe repr-string for the given object. + + As with saferepr, failing __repr__ functions of user instances + will be represented with a short exception info. + + This function is a wrapper around simple repr. + + Note: a cleaner solution would be to alter ``saferepr``this way + when maxsize=None, but that might affect some other code. + """ + try: + if use_ascii: + return ascii(obj) + return repr(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) diff --git a/.venv/Lib/site-packages/_pytest/_io/terminalwriter.py b/.venv/Lib/site-packages/_pytest/_io/terminalwriter.py new file mode 100644 index 0000000..70ebd3d --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_io/terminalwriter.py @@ -0,0 +1,274 @@ +"""Helper functions for writing to terminals and files.""" + +from __future__ import annotations + +import os +import shutil +import sys +from typing import final +from typing import Literal +from typing import Sequence +from typing import TextIO +from typing import TYPE_CHECKING + +from ..compat import assert_never +from .wcwidth import wcswidth + + +if TYPE_CHECKING: + from pygments.formatter import Formatter + from pygments.lexer import Lexer + + +# This code was initially copied from py 1.8.1, file _io/terminalwriter.py. + + +def get_terminal_width() -> int: + width, _ = shutil.get_terminal_size(fallback=(80, 24)) + + # The Windows get_terminal_size may be bogus, let's sanify a bit. + if width < 40: + width = 80 + + return width + + +def should_do_markup(file: TextIO) -> bool: + if os.environ.get("PY_COLORS") == "1": + return True + if os.environ.get("PY_COLORS") == "0": + return False + if os.environ.get("NO_COLOR"): + return False + if os.environ.get("FORCE_COLOR"): + return True + return ( + hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" + ) + + +@final +class TerminalWriter: + _esctable = dict( + black=30, + red=31, + green=32, + yellow=33, + blue=34, + purple=35, + cyan=36, + white=37, + Black=40, + Red=41, + Green=42, + Yellow=43, + Blue=44, + Purple=45, + Cyan=46, + White=47, + bold=1, + light=2, + blink=5, + invert=7, + ) + + def __init__(self, file: TextIO | None = None) -> None: + if file is None: + file = sys.stdout + if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": + try: + import colorama + except ImportError: + pass + else: + file = colorama.AnsiToWin32(file).stream + assert file is not None + self._file = file + self.hasmarkup = should_do_markup(file) + self._current_line = "" + self._terminal_width: int | None = None + self.code_highlight = True + + @property + def fullwidth(self) -> int: + if self._terminal_width is not None: + return self._terminal_width + return get_terminal_width() + + @fullwidth.setter + def fullwidth(self, value: int) -> None: + self._terminal_width = value + + @property + def width_of_current_line(self) -> int: + """Return an estimate of the width so far in the current line.""" + return wcswidth(self._current_line) + + def markup(self, text: str, **markup: bool) -> str: + for name in markup: + if name not in self._esctable: + raise ValueError(f"unknown markup: {name!r}") + if self.hasmarkup: + esc = [self._esctable[name] for name, on in markup.items() if on] + if esc: + text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m" + return text + + def sep( + self, + sepchar: str, + title: str | None = None, + fullwidth: int | None = None, + **markup: bool, + ) -> None: + if fullwidth is None: + fullwidth = self.fullwidth + # The goal is to have the line be as long as possible + # under the condition that len(line) <= fullwidth. + if sys.platform == "win32": + # If we print in the last column on windows we are on a + # new line but there is no way to verify/neutralize this + # (we may not know the exact line width). + # So let's be defensive to avoid empty lines in the output. + fullwidth -= 1 + if title is not None: + # we want 2 + 2*len(fill) + len(title) <= fullwidth + # i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth + # 2*len(sepchar)*N <= fullwidth - len(title) - 2 + # N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) + N = max((fullwidth - len(title) - 2) // (2 * len(sepchar)), 1) + fill = sepchar * N + line = f"{fill} {title} {fill}" + else: + # we want len(sepchar)*N <= fullwidth + # i.e. N <= fullwidth // len(sepchar) + line = sepchar * (fullwidth // len(sepchar)) + # In some situations there is room for an extra sepchar at the right, + # in particular if we consider that with a sepchar like "_ " the + # trailing space is not important at the end of the line. + if len(line) + len(sepchar.rstrip()) <= fullwidth: + line += sepchar.rstrip() + + self.line(line, **markup) + + def write(self, msg: str, *, flush: bool = False, **markup: bool) -> None: + if msg: + current_line = msg.rsplit("\n", 1)[-1] + if "\n" in msg: + self._current_line = current_line + else: + self._current_line += current_line + + msg = self.markup(msg, **markup) + + try: + self._file.write(msg) + except UnicodeEncodeError: + # Some environments don't support printing general Unicode + # strings, due to misconfiguration or otherwise; in that case, + # print the string escaped to ASCII. + # When the Unicode situation improves we should consider + # letting the error propagate instead of masking it (see #7475 + # for one brief attempt). + msg = msg.encode("unicode-escape").decode("ascii") + self._file.write(msg) + + if flush: + self.flush() + + def line(self, s: str = "", **markup: bool) -> None: + self.write(s, **markup) + self.write("\n") + + def flush(self) -> None: + self._file.flush() + + def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> None: + """Write lines of source code possibly highlighted. + + Keeping this private for now because the API is clunky. We should discuss how + to evolve the terminal writer so we can have more precise color support, for example + being able to write part of a line in one color and the rest in another, and so on. + """ + if indents and len(indents) != len(lines): + raise ValueError( + f"indents size ({len(indents)}) should have same size as lines ({len(lines)})" + ) + if not indents: + indents = [""] * len(lines) + source = "\n".join(lines) + new_lines = self._highlight(source).splitlines() + for indent, new_line in zip(indents, new_lines): + self.line(indent + new_line) + + def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer | None: + try: + if lexer == "python": + from pygments.lexers.python import PythonLexer + + return PythonLexer() + elif lexer == "diff": + from pygments.lexers.diff import DiffLexer + + return DiffLexer() + else: + assert_never(lexer) + except ModuleNotFoundError: + return None + + def _get_pygments_formatter(self) -> Formatter | None: + try: + import pygments.util + except ModuleNotFoundError: + return None + + from _pytest.config.exceptions import UsageError + + theme = os.getenv("PYTEST_THEME") + theme_mode = os.getenv("PYTEST_THEME_MODE", "dark") + + try: + from pygments.formatters.terminal import TerminalFormatter + + return TerminalFormatter(bg=theme_mode, style=theme) + + except pygments.util.ClassNotFound as e: + raise UsageError( + f"PYTEST_THEME environment variable has an invalid value: '{theme}'. " + "Hint: See available pygments styles with `pygmentize -L styles`." + ) from e + except pygments.util.OptionError as e: + raise UsageError( + f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. " + "The allowed values are 'dark' (default) and 'light'." + ) from e + + def _highlight( + self, source: str, lexer: Literal["diff", "python"] = "python" + ) -> str: + """Highlight the given source if we have markup support.""" + if not source or not self.hasmarkup or not self.code_highlight: + return source + + pygments_lexer = self._get_pygments_lexer(lexer) + if pygments_lexer is None: + return source + + pygments_formatter = self._get_pygments_formatter() + if pygments_formatter is None: + return source + + from pygments import highlight + + highlighted: str = highlight(source, pygments_lexer, pygments_formatter) + # pygments terminal formatter may add a newline when there wasn't one. + # We don't want this, remove. + if highlighted[-1] == "\n" and source[-1] != "\n": + highlighted = highlighted[:-1] + + # Some lexers will not set the initial color explicitly + # which may lead to the previous color being propagated to the + # start of the expression, so reset first. + highlighted = "\x1b[0m" + highlighted + + return highlighted diff --git a/.venv/Lib/site-packages/_pytest/_io/wcwidth.py b/.venv/Lib/site-packages/_pytest/_io/wcwidth.py new file mode 100644 index 0000000..23886ff --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_io/wcwidth.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from functools import lru_cache +import unicodedata + + +@lru_cache(100) +def wcwidth(c: str) -> int: + """Determine how many columns are needed to display a character in a terminal. + + Returns -1 if the character is not printable. + Returns 0, 1 or 2 for other characters. + """ + o = ord(c) + + # ASCII fast path. + if 0x20 <= o < 0x07F: + return 1 + + # Some Cf/Zp/Zl characters which should be zero-width. + if ( + o == 0x0000 + or 0x200B <= o <= 0x200F + or 0x2028 <= o <= 0x202E + or 0x2060 <= o <= 0x2063 + ): + return 0 + + category = unicodedata.category(c) + + # Control characters. + if category == "Cc": + return -1 + + # Combining characters with zero width. + if category in ("Me", "Mn"): + return 0 + + # Full/Wide east asian characters. + if unicodedata.east_asian_width(c) in ("F", "W"): + return 2 + + return 1 + + +def wcswidth(s: str) -> int: + """Determine how many columns are needed to display a string in a terminal. + + Returns -1 if the string contains non-printable characters. + """ + width = 0 + for c in unicodedata.normalize("NFC", s): + wc = wcwidth(c) + if wc < 0: + return -1 + width += wc + return width diff --git a/.venv/Lib/site-packages/_pytest/_py/__init__.py b/.venv/Lib/site-packages/_pytest/_py/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/_pytest/_py/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/_py/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a442363bf0e50c20d972ad810c20b47a3bd36741 GIT binary patch literal 208 zcmX@j%ge<81k01HGC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!%6GPk2`x@7Dvk+G z%#CqLEzT~paZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%Ii#1~YSq!yRx18M#E_{_Y_lK6PNg34PQHo5sJ br8%i~MXW&k8G*PM#Q4a}$jDg43}gWSh5%$@h% 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 VZnN_4c!8P+lY}!ko)g%T`fr(J@|pku literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/_py/error.py b/.venv/Lib/site-packages/_pytest/_py/error.py new file mode 100644 index 0000000..ab3a4ed --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_py/error.py @@ -0,0 +1,111 @@ +"""create errno-specific classes for IO or os calls.""" + +from __future__ import annotations + +import errno +import os +import sys +from typing import Callable +from typing import TYPE_CHECKING +from typing import TypeVar + + +if TYPE_CHECKING: + from typing_extensions import ParamSpec + + P = ParamSpec("P") + +R = TypeVar("R") + + +class Error(EnvironmentError): + def __repr__(self) -> str: + return "{}.{} {!r}: {} ".format( + self.__class__.__module__, + self.__class__.__name__, + self.__class__.__doc__, + " ".join(map(str, self.args)), + # repr(self.args) + ) + + def __str__(self) -> str: + s = "[{}]: {}".format( + self.__class__.__doc__, + " ".join(map(str, self.args)), + ) + return s + + +_winerrnomap = { + 2: errno.ENOENT, + 3: errno.ENOENT, + 17: errno.EEXIST, + 18: errno.EXDEV, + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailable + 22: errno.ENOTDIR, + 20: errno.ENOTDIR, + 267: errno.ENOTDIR, + 5: errno.EACCES, # anything better? +} + + +class ErrorMaker: + """lazily provides Exception classes for each possible POSIX errno + (as defined per the 'errno' module). All such instances + subclass EnvironmentError. + """ + + _errno2class: dict[int, type[Error]] = {} + + def __getattr__(self, name: str) -> type[Error]: + if name[0] == "_": + raise AttributeError(name) + eno = getattr(errno, name) + cls = self._geterrnoclass(eno) + setattr(self, name, cls) + return cls + + def _geterrnoclass(self, eno: int) -> type[Error]: + try: + return self._errno2class[eno] + except KeyError: + clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,)) + errorcls = type( + clsname, + (Error,), + {"__module__": "py.error", "__doc__": os.strerror(eno)}, + ) + self._errno2class[eno] = errorcls + return errorcls + + def checked_call( + self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs + ) -> R: + """Call a function and raise an errno-exception if applicable.""" + __tracebackhide__ = True + try: + return func(*args, **kwargs) + except Error: + raise + except OSError as value: + if not hasattr(value, "errno"): + raise + errno = value.errno + if sys.platform == "win32": + try: + cls = self._geterrnoclass(_winerrnomap[errno]) + except KeyError: + raise value + else: + # we are not on Windows, or we got a proper OSError + cls = self._geterrnoclass(errno) + + raise cls(f"{func.__name__}{args!r}") + + +_error_maker = ErrorMaker() +checked_call = _error_maker.checked_call + + +def __getattr__(attr: str) -> type[Error]: + return getattr(_error_maker, attr) # type: ignore[no-any-return] diff --git a/.venv/Lib/site-packages/_pytest/_py/path.py b/.venv/Lib/site-packages/_pytest/_py/path.py new file mode 100644 index 0000000..c7ab118 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_py/path.py @@ -0,0 +1,1475 @@ +# mypy: allow-untyped-defs +"""local path implementation.""" + +from __future__ import annotations + +import atexit +from contextlib import contextmanager +import fnmatch +import importlib.util +import io +import os +from os.path import abspath +from os.path import dirname +from os.path import exists +from os.path import isabs +from os.path import isdir +from os.path import isfile +from os.path import islink +from os.path import normpath +import posixpath +from stat import S_ISDIR +from stat import S_ISLNK +from stat import S_ISREG +import sys +from typing import Any +from typing import Callable +from typing import cast +from typing import Literal +from typing import overload +from typing import TYPE_CHECKING +import uuid +import warnings + +from . import error + + +# Moved from local.py. +iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt") + + +class Checkers: + _depend_on_existence = "exists", "link", "dir", "file" + + def __init__(self, path): + self.path = path + + def dotfile(self): + return self.path.basename.startswith(".") + + def ext(self, arg): + if not arg.startswith("."): + arg = "." + arg + return self.path.ext == arg + + def basename(self, arg): + return self.path.basename == arg + + def basestarts(self, arg): + return self.path.basename.startswith(arg) + + def relto(self, arg): + return self.path.relto(arg) + + def fnmatch(self, arg): + return self.path.fnmatch(arg) + + def endswith(self, arg): + return str(self.path).endswith(arg) + + def _evaluate(self, kw): + from .._code.source import getrawcode + + for name, value in kw.items(): + invert = False + meth = None + try: + meth = getattr(self, name) + except AttributeError: + if name[:3] == "not": + invert = True + try: + meth = getattr(self, name[3:]) + except AttributeError: + pass + if meth is None: + raise TypeError(f"no {name!r} checker available for {self.path!r}") + try: + if getrawcode(meth).co_argcount > 1: + if (not meth(value)) ^ invert: + return False + else: + if bool(value) ^ bool(meth()) ^ invert: + return False + except (error.ENOENT, error.ENOTDIR, error.EBUSY): + # EBUSY feels not entirely correct, + # but its kind of necessary since ENOMEDIUM + # is not accessible in python + for name in self._depend_on_existence: + if name in kw: + if kw.get(name): + return False + name = "not" + name + if name in kw: + if not kw.get(name): + return False + return True + + _statcache: Stat + + def _stat(self) -> Stat: + try: + return self._statcache + except AttributeError: + try: + self._statcache = self.path.stat() + except error.ELOOP: + self._statcache = self.path.lstat() + return self._statcache + + def dir(self): + return S_ISDIR(self._stat().mode) + + def file(self): + return S_ISREG(self._stat().mode) + + def exists(self): + return self._stat() + + def link(self): + st = self.path.lstat() + return S_ISLNK(st.mode) + + +class NeverRaised(Exception): + pass + + +class Visitor: + def __init__(self, fil, rec, ignore, bf, sort): + if isinstance(fil, str): + fil = FNMatcher(fil) + if isinstance(rec, str): + self.rec: Callable[[LocalPath], bool] = FNMatcher(rec) + elif not hasattr(rec, "__call__") and rec: + self.rec = lambda path: True + else: + self.rec = rec + self.fil = fil + self.ignore = ignore + self.breadthfirst = bf + self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x) + + def gen(self, path): + try: + entries = path.listdir() + except self.ignore: + return + rec = self.rec + dirs = self.optsort( + [p for p in entries if p.check(dir=1) and (rec is None or rec(p))] + ) + if not self.breadthfirst: + for subdir in dirs: + yield from self.gen(subdir) + for p in self.optsort(entries): + if self.fil is None or self.fil(p): + yield p + if self.breadthfirst: + for subdir in dirs: + yield from self.gen(subdir) + + +class FNMatcher: + def __init__(self, pattern): + self.pattern = pattern + + def __call__(self, path): + pattern = self.pattern + + if ( + pattern.find(path.sep) == -1 + and iswin32 + and pattern.find(posixpath.sep) != -1 + ): + # Running on Windows, the pattern has no Windows path separators, + # and the pattern has one or more Posix path separators. Replace + # the Posix path separators with the Windows path separator. + pattern = pattern.replace(posixpath.sep, path.sep) + + if pattern.find(path.sep) == -1: + name = path.basename + else: + name = str(path) # path.strpath # XXX svn? + if not os.path.isabs(pattern): + pattern = "*" + path.sep + pattern + return fnmatch.fnmatch(name, pattern) + + +def map_as_list(func, iter): + return list(map(func, iter)) + + +class Stat: + if TYPE_CHECKING: + + @property + def size(self) -> int: ... + + @property + def mtime(self) -> float: ... + + def __getattr__(self, name: str) -> Any: + return getattr(self._osstatresult, "st_" + name) + + def __init__(self, path, osstatresult): + self.path = path + self._osstatresult = osstatresult + + @property + def owner(self): + if iswin32: + raise NotImplementedError("XXX win32") + import pwd + + entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined,unused-ignore] + return entry[0] + + @property + def group(self): + """Return group name of file.""" + if iswin32: + raise NotImplementedError("XXX win32") + import grp + + entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined,unused-ignore] + return entry[0] + + def isdir(self): + return S_ISDIR(self._osstatresult.st_mode) + + def isfile(self): + return S_ISREG(self._osstatresult.st_mode) + + def islink(self): + self.path.lstat() + return S_ISLNK(self._osstatresult.st_mode) + + +def getuserid(user): + import pwd + + if not isinstance(user, int): + user = pwd.getpwnam(user)[2] # type:ignore[attr-defined,unused-ignore] + return user + + +def getgroupid(group): + import grp + + if not isinstance(group, int): + group = grp.getgrnam(group)[2] # type:ignore[attr-defined,unused-ignore] + return group + + +class LocalPath: + """Object oriented interface to os.path and other local filesystem + related information. + """ + + class ImportMismatchError(ImportError): + """raised on pyimport() if there is a mismatch of __file__'s""" + + sep = os.sep + + def __init__(self, path=None, expanduser=False): + """Initialize and return a local Path instance. + + Path can be relative to the current directory. + If path is None it defaults to the current working directory. + If expanduser is True, tilde-expansion is performed. + Note that Path instances always carry an absolute path. + Note also that passing in a local path object will simply return + the exact same path object. Use new() to get a new copy. + """ + if path is None: + self.strpath = error.checked_call(os.getcwd) + else: + try: + path = os.fspath(path) + except TypeError: + raise ValueError( + "can only pass None, Path instances " + "or non-empty strings to LocalPath" + ) + if expanduser: + path = os.path.expanduser(path) + self.strpath = abspath(path) + + if sys.platform != "win32": + + def chown(self, user, group, rec=0): + """Change ownership to the given user and group. + user and group may be specified by a number or + by a name. if rec is True change ownership + recursively. + """ + uid = getuserid(user) + gid = getgroupid(group) + if rec: + for x in self.visit(rec=lambda x: x.check(link=0)): + if x.check(link=0): + error.checked_call(os.chown, str(x), uid, gid) + error.checked_call(os.chown, str(self), uid, gid) + + def readlink(self) -> str: + """Return value of a symbolic link.""" + # https://github.com/python/mypy/issues/12278 + return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value,unused-ignore] + + def mklinkto(self, oldname): + """Posix style hard link to another name.""" + error.checked_call(os.link, str(oldname), str(self)) + + def mksymlinkto(self, value, absolute=1): + """Create a symbolic link with the given value (pointing to another name).""" + if absolute: + error.checked_call(os.symlink, str(value), self.strpath) + else: + base = self.common(value) + # with posix local paths '/' is always a common base + relsource = self.__class__(value).relto(base) + reldest = self.relto(base) + n = reldest.count(self.sep) + target = self.sep.join(("..",) * n + (relsource,)) + error.checked_call(os.symlink, target, self.strpath) + + def __div__(self, other): + return self.join(os.fspath(other)) + + __truediv__ = __div__ # py3k + + @property + def basename(self): + """Basename part of path.""" + return self._getbyspec("basename")[0] + + @property + def dirname(self): + """Dirname part of path.""" + return self._getbyspec("dirname")[0] + + @property + def purebasename(self): + """Pure base name of the path.""" + return self._getbyspec("purebasename")[0] + + @property + def ext(self): + """Extension of the path (including the '.').""" + return self._getbyspec("ext")[0] + + def read_binary(self): + """Read and return a bytestring from reading the path.""" + with self.open("rb") as f: + return f.read() + + def read_text(self, encoding): + """Read and return a Unicode string from reading the path.""" + with self.open("r", encoding=encoding) as f: + return f.read() + + def read(self, mode="r"): + """Read and return a bytestring from reading the path.""" + with self.open(mode) as f: + return f.read() + + def readlines(self, cr=1): + """Read and return a list of lines from the path. if cr is False, the + newline will be removed from the end of each line.""" + mode = "r" + + if not cr: + content = self.read(mode) + return content.split("\n") + else: + f = self.open(mode) + try: + return f.readlines() + finally: + f.close() + + def load(self): + """(deprecated) return object unpickled from self.read()""" + f = self.open("rb") + try: + import pickle + + return error.checked_call(pickle.load, f) + finally: + f.close() + + def move(self, target): + """Move this path to target.""" + if target.relto(self): + raise error.EINVAL(target, "cannot move path into a subdirectory of itself") + try: + self.rename(target) + except error.EXDEV: # invalid cross-device link + self.copy(target) + self.remove() + + def fnmatch(self, pattern): + """Return true if the basename/fullname matches the glob-'pattern'. + + valid pattern characters:: + + * matches everything + ? matches any single character + [seq] matches any character in seq + [!seq] matches any char not in seq + + If the pattern contains a path-separator then the full path + is used for pattern matching and a '*' is prepended to the + pattern. + + if the pattern doesn't contain a path-separator the pattern + is only matched against the basename. + """ + return FNMatcher(pattern)(self) + + def relto(self, relpath): + """Return a string which is the relative part of the path + to the given 'relpath'. + """ + if not isinstance(relpath, (str, LocalPath)): + raise TypeError(f"{relpath!r}: not a string or path object") + strrelpath = str(relpath) + if strrelpath and strrelpath[-1] != self.sep: + strrelpath += self.sep + # assert strrelpath[-1] == self.sep + # assert strrelpath[-2] != self.sep + strself = self.strpath + if sys.platform == "win32" or getattr(os, "_name", None) == "nt": + if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)): + return strself[len(strrelpath) :] + elif strself.startswith(strrelpath): + return strself[len(strrelpath) :] + return "" + + def ensure_dir(self, *args): + """Ensure the path joined with args is a directory.""" + return self.ensure(*args, dir=True) + + def bestrelpath(self, dest): + """Return a string which is a relative path from self + (assumed to be a directory) to dest such that + self.join(bestrelpath) == dest and if not such + path can be determined return dest. + """ + try: + if self == dest: + return os.curdir + base = self.common(dest) + if not base: # can be the case on windows + return str(dest) + self2base = self.relto(base) + reldest = dest.relto(base) + if self2base: + n = self2base.count(self.sep) + 1 + else: + n = 0 + lst = [os.pardir] * n + if reldest: + lst.append(reldest) + target = dest.sep.join(lst) + return target + except AttributeError: + return str(dest) + + def exists(self): + return self.check() + + def isdir(self): + return self.check(dir=1) + + def isfile(self): + return self.check(file=1) + + def parts(self, reverse=False): + """Return a root-first list of all ancestor directories + plus the path itself. + """ + current = self + lst = [self] + while 1: + last = current + current = current.dirpath() + if last == current: + break + lst.append(current) + if not reverse: + lst.reverse() + return lst + + def common(self, other): + """Return the common part shared with the other path + or None if there is no common part. + """ + last = None + for x, y in zip(self.parts(), other.parts()): + if x != y: + return last + last = x + return last + + def __add__(self, other): + """Return new path object with 'other' added to the basename""" + return self.new(basename=self.basename + str(other)) + + def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False): + """Yields all paths below the current one + + fil is a filter (glob pattern or callable), if not matching the + path will not be yielded, defaulting to None (everything is + returned) + + rec is a filter (glob pattern or callable) that controls whether + a node is descended, defaulting to None + + ignore is an Exception class that is ignoredwhen calling dirlist() + on any of the paths (by default, all exceptions are reported) + + bf if True will cause a breadthfirst search instead of the + default depthfirst. Default: False + + sort if True will sort entries within each directory level. + """ + yield from Visitor(fil, rec, ignore, bf, sort).gen(self) + + def _sortlist(self, res, sort): + if sort: + if hasattr(sort, "__call__"): + warnings.warn( + DeprecationWarning( + "listdir(sort=callable) is deprecated and breaks on python3" + ), + stacklevel=3, + ) + res.sort(sort) + else: + res.sort() + + def __fspath__(self): + return self.strpath + + def __hash__(self): + s = self.strpath + if iswin32: + s = s.lower() + return hash(s) + + def __eq__(self, other): + s1 = os.fspath(self) + try: + s2 = os.fspath(other) + except TypeError: + return False + if iswin32: + s1 = s1.lower() + try: + s2 = s2.lower() + except AttributeError: + return False + return s1 == s2 + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return os.fspath(self) < os.fspath(other) + + def __gt__(self, other): + return os.fspath(self) > os.fspath(other) + + def samefile(self, other): + """Return True if 'other' references the same file as 'self'.""" + other = os.fspath(other) + if not isabs(other): + other = abspath(other) + if self == other: + return True + if not hasattr(os.path, "samefile"): + return False + return error.checked_call(os.path.samefile, self.strpath, other) + + def remove(self, rec=1, ignore_errors=False): + """Remove a file or directory (or a directory tree if rec=1). + if ignore_errors is True, errors while removing directories will + be ignored. + """ + if self.check(dir=1, link=0): + if rec: + # force remove of readonly files on windows + if iswin32: + self.chmod(0o700, rec=1) + import shutil + + error.checked_call( + shutil.rmtree, self.strpath, ignore_errors=ignore_errors + ) + else: + error.checked_call(os.rmdir, self.strpath) + else: + if iswin32: + self.chmod(0o700) + error.checked_call(os.remove, self.strpath) + + def computehash(self, hashtype="md5", chunksize=524288): + """Return hexdigest of hashvalue for this file.""" + try: + try: + import hashlib as mod + except ImportError: + if hashtype == "sha1": + hashtype = "sha" + mod = __import__(hashtype) + hash = getattr(mod, hashtype)() + except (AttributeError, ImportError): + raise ValueError(f"Don't know how to compute {hashtype!r} hash") + f = self.open("rb") + try: + while 1: + buf = f.read(chunksize) + if not buf: + return hash.hexdigest() + hash.update(buf) + finally: + f.close() + + def new(self, **kw): + """Create a modified version of this path. + the following keyword arguments modify various path parts:: + + a:/some/path/to/a/file.ext + xx drive + xxxxxxxxxxxxxxxxx dirname + xxxxxxxx basename + xxxx purebasename + xxx ext + """ + obj = object.__new__(self.__class__) + if not kw: + obj.strpath = self.strpath + return obj + drive, dirname, basename, purebasename, ext = self._getbyspec( + "drive,dirname,basename,purebasename,ext" + ) + if "basename" in kw: + if "purebasename" in kw or "ext" in kw: + raise ValueError(f"invalid specification {kw!r}") + else: + pb = kw.setdefault("purebasename", purebasename) + try: + ext = kw["ext"] + except KeyError: + pass + else: + if ext and not ext.startswith("."): + ext = "." + ext + kw["basename"] = pb + ext + + if "dirname" in kw and not kw["dirname"]: + kw["dirname"] = drive + else: + kw.setdefault("dirname", dirname) + kw.setdefault("sep", self.sep) + obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw)) + return obj + + def _getbyspec(self, spec: str) -> list[str]: + """See new for what 'spec' can be.""" + res = [] + parts = self.strpath.split(self.sep) + + args = filter(None, spec.split(",")) + for name in args: + if name == "drive": + res.append(parts[0]) + elif name == "dirname": + res.append(self.sep.join(parts[:-1])) + else: + basename = parts[-1] + if name == "basename": + res.append(basename) + else: + i = basename.rfind(".") + if i == -1: + purebasename, ext = basename, "" + else: + purebasename, ext = basename[:i], basename[i:] + if name == "purebasename": + res.append(purebasename) + elif name == "ext": + res.append(ext) + else: + raise ValueError(f"invalid part specification {name!r}") + return res + + def dirpath(self, *args, **kwargs): + """Return the directory path joined with any given path arguments.""" + if not kwargs: + path = object.__new__(self.__class__) + path.strpath = dirname(self.strpath) + if args: + path = path.join(*args) + return path + return self.new(basename="").join(*args, **kwargs) + + def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath: + """Return a new path by appending all 'args' as path + components. if abs=1 is used restart from root if any + of the args is an absolute path. + """ + sep = self.sep + strargs = [os.fspath(arg) for arg in args] + strpath = self.strpath + if abs: + newargs: list[str] = [] + for arg in reversed(strargs): + if isabs(arg): + strpath = arg + strargs = newargs + break + newargs.insert(0, arg) + # special case for when we have e.g. strpath == "/" + actual_sep = "" if strpath.endswith(sep) else sep + for arg in strargs: + arg = arg.strip(sep) + if iswin32: + # allow unix style paths even on windows. + arg = arg.strip("/") + arg = arg.replace("/", sep) + strpath = strpath + actual_sep + arg + actual_sep = sep + obj = object.__new__(self.__class__) + obj.strpath = normpath(strpath) + return obj + + def open(self, mode="r", ensure=False, encoding=None): + """Return an opened file with the given mode. + + If ensure is True, create parent directories if needed. + """ + if ensure: + self.dirpath().ensure(dir=1) + if encoding: + return error.checked_call( + io.open, + self.strpath, + mode, + encoding=encoding, + ) + return error.checked_call(open, self.strpath, mode) + + def _fastjoin(self, name): + child = object.__new__(self.__class__) + child.strpath = self.strpath + self.sep + name + return child + + def islink(self): + return islink(self.strpath) + + def check(self, **kw): + """Check a path for existence and properties. + + Without arguments, return True if the path exists, otherwise False. + + valid checkers:: + + file = 1 # is a file + file = 0 # is not a file (may not even exist) + dir = 1 # is a dir + link = 1 # is a link + exists = 1 # exists + + You can specify multiple checker definitions, for example:: + + path.check(file=1, link=1) # a link pointing to a file + """ + if not kw: + return exists(self.strpath) + if len(kw) == 1: + if "dir" in kw: + return not kw["dir"] ^ isdir(self.strpath) + if "file" in kw: + return not kw["file"] ^ isfile(self.strpath) + if not kw: + kw = {"exists": 1} + return Checkers(self)._evaluate(kw) + + _patternchars = set("*?[" + os.sep) + + def listdir(self, fil=None, sort=None): + """List directory contents, possibly filter by the given fil func + and possibly sorted. + """ + if fil is None and sort is None: + names = error.checked_call(os.listdir, self.strpath) + return map_as_list(self._fastjoin, names) + if isinstance(fil, str): + if not self._patternchars.intersection(fil): + child = self._fastjoin(fil) + if exists(child.strpath): + return [child] + return [] + fil = FNMatcher(fil) + names = error.checked_call(os.listdir, self.strpath) + res = [] + for name in names: + child = self._fastjoin(name) + if fil is None or fil(child): + res.append(child) + self._sortlist(res, sort) + return res + + def size(self) -> int: + """Return size of the underlying file object""" + return self.stat().size + + def mtime(self) -> float: + """Return last modification time of the path.""" + return self.stat().mtime + + def copy(self, target, mode=False, stat=False): + """Copy path to target. + + If mode is True, will copy permission from path to target. + If stat is True, copy permission, last modification + time, last access time, and flags from path to target. + """ + if self.check(file=1): + if target.check(dir=1): + target = target.join(self.basename) + assert self != target + copychunked(self, target) + if mode: + copymode(self.strpath, target.strpath) + if stat: + copystat(self, target) + else: + + def rec(p): + return p.check(link=0) + + for x in self.visit(rec=rec): + relpath = x.relto(self) + newx = target.join(relpath) + newx.dirpath().ensure(dir=1) + if x.check(link=1): + newx.mksymlinkto(x.readlink()) + continue + elif x.check(file=1): + copychunked(x, newx) + elif x.check(dir=1): + newx.ensure(dir=1) + if mode: + copymode(x.strpath, newx.strpath) + if stat: + copystat(x, newx) + + def rename(self, target): + """Rename this path to target.""" + target = os.fspath(target) + return error.checked_call(os.rename, self.strpath, target) + + def dump(self, obj, bin=1): + """Pickle object into path location""" + f = self.open("wb") + import pickle + + try: + error.checked_call(pickle.dump, obj, f, bin) + finally: + f.close() + + def mkdir(self, *args): + """Create & return the directory joined with args.""" + p = self.join(*args) + error.checked_call(os.mkdir, os.fspath(p)) + return p + + def write_binary(self, data, ensure=False): + """Write binary data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("wb") as f: + f.write(data) + + def write_text(self, data, encoding, ensure=False): + """Write text data into path using the specified encoding. + If ensure is True create missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("w", encoding=encoding) as f: + f.write(data) + + def write(self, data, mode="w", ensure=False): + """Write data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + if "b" in mode: + if not isinstance(data, bytes): + raise ValueError("can only process bytes") + else: + if not isinstance(data, str): + if not isinstance(data, bytes): + data = str(data) + else: + data = data.decode(sys.getdefaultencoding()) + f = self.open(mode) + try: + f.write(data) + finally: + f.close() + + def _ensuredirs(self): + parent = self.dirpath() + if parent == self: + return self + if parent.check(dir=0): + parent._ensuredirs() + if self.check(dir=0): + try: + self.mkdir() + except error.EEXIST: + # race condition: file/dir created by another thread/process. + # complain if it is not a dir + if self.check(dir=0): + raise + return self + + def ensure(self, *args, **kwargs): + """Ensure that an args-joined path exists (by default as + a file). if you specify a keyword argument 'dir=True' + then the path is forced to be a directory path. + """ + p = self.join(*args) + if kwargs.get("dir", 0): + return p._ensuredirs() + else: + p.dirpath()._ensuredirs() + if not p.check(file=1): + p.open("wb").close() + return p + + @overload + def stat(self, raising: Literal[True] = ...) -> Stat: ... + + @overload + def stat(self, raising: Literal[False]) -> Stat | None: ... + + def stat(self, raising: bool = True) -> Stat | None: + """Return an os.stat() tuple.""" + if raising: + return Stat(self, error.checked_call(os.stat, self.strpath)) + try: + return Stat(self, os.stat(self.strpath)) + except KeyboardInterrupt: + raise + except Exception: + return None + + def lstat(self) -> Stat: + """Return an os.lstat() tuple.""" + return Stat(self, error.checked_call(os.lstat, self.strpath)) + + def setmtime(self, mtime=None): + """Set modification time for the given path. if 'mtime' is None + (the default) then the file's mtime is set to current time. + + Note that the resolution for 'mtime' is platform dependent. + """ + if mtime is None: + return error.checked_call(os.utime, self.strpath, mtime) + try: + return error.checked_call(os.utime, self.strpath, (-1, mtime)) + except error.EINVAL: + return error.checked_call(os.utime, self.strpath, (self.atime(), mtime)) + + def chdir(self): + """Change directory to self and return old current directory""" + try: + old = self.__class__() + except error.ENOENT: + old = None + error.checked_call(os.chdir, self.strpath) + return old + + @contextmanager + def as_cwd(self): + """ + Return a context manager, which changes to the path's dir during the + managed "with" context. + On __enter__ it returns the old dir, which might be ``None``. + """ + old = self.chdir() + try: + yield old + finally: + if old is not None: + old.chdir() + + def realpath(self): + """Return a new path which contains no symbolic links.""" + return self.__class__(os.path.realpath(self.strpath)) + + def atime(self): + """Return last access time of the path.""" + return self.stat().atime + + def __repr__(self): + return f"local({self.strpath!r})" + + def __str__(self): + """Return string representation of the Path.""" + return self.strpath + + def chmod(self, mode, rec=0): + """Change permissions to the given mode. If mode is an + integer it directly encodes the os-specific modes. + if rec is True perform recursively. + """ + if not isinstance(mode, int): + raise TypeError(f"mode {mode!r} must be an integer") + if rec: + for x in self.visit(rec=rec): + error.checked_call(os.chmod, str(x), mode) + error.checked_call(os.chmod, self.strpath, mode) + + def pypkgpath(self): + """Return the Python package path by looking for the last + directory upwards which still contains an __init__.py. + Return None if a pkgpath cannot be determined. + """ + pkgpath = None + for parent in self.parts(reverse=True): + if parent.isdir(): + if not parent.join("__init__.py").exists(): + break + if not isimportable(parent.basename): + break + pkgpath = parent + return pkgpath + + def _ensuresyspath(self, ensuremode, path): + if ensuremode: + s = str(path) + if ensuremode == "append": + if s not in sys.path: + sys.path.append(s) + else: + if s != sys.path[0]: + sys.path.insert(0, s) + + def pyimport(self, modname=None, ensuresyspath=True): + """Return path as an imported python module. + + If modname is None, look for the containing package + and construct an according module name. + The module will be put/looked up in sys.modules. + if ensuresyspath is True then the root dir for importing + the file (taking __init__.py files into account) will + be prepended to sys.path if it isn't there already. + If ensuresyspath=="append" the root dir will be appended + if it isn't already contained in sys.path. + if ensuresyspath is False no modification of syspath happens. + + Special value of ensuresyspath=="importlib" is intended + purely for using in pytest, it is capable only of importing + separate .py files outside packages, e.g. for test suite + without any __init__.py file. It effectively allows having + same-named test modules in different places and offers + mild opt-in via this option. Note that it works only in + recent versions of python. + """ + if not self.check(): + raise error.ENOENT(self) + + if ensuresyspath == "importlib": + if modname is None: + modname = self.purebasename + spec = importlib.util.spec_from_file_location(modname, str(self)) + if spec is None or spec.loader is None: + raise ImportError(f"Can't find module {modname} at location {self!s}") + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + pkgpath = None + if modname is None: + pkgpath = self.pypkgpath() + if pkgpath is not None: + pkgroot = pkgpath.dirpath() + names = self.new(ext="").relto(pkgroot).split(self.sep) + if names[-1] == "__init__": + names.pop() + modname = ".".join(names) + else: + pkgroot = self.dirpath() + modname = self.purebasename + + self._ensuresyspath(ensuresyspath, pkgroot) + __import__(modname) + mod = sys.modules[modname] + if self.basename == "__init__.py": + return mod # we don't check anything as we might + # be in a namespace package ... too icky to check + modfile = mod.__file__ + assert modfile is not None + if modfile[-4:] in (".pyc", ".pyo"): + modfile = modfile[:-1] + elif modfile.endswith("$py.class"): + modfile = modfile[:-9] + ".py" + if modfile.endswith(os.sep + "__init__.py"): + if self.basename != "__init__.py": + modfile = modfile[:-12] + try: + issame = self.samefile(modfile) + except error.ENOENT: + issame = False + if not issame: + ignore = os.getenv("PY_IGNORE_IMPORTMISMATCH") + if ignore != "1": + raise self.ImportMismatchError(modname, modfile, self) + return mod + else: + try: + return sys.modules[modname] + except KeyError: + # we have a custom modname, do a pseudo-import + import types + + mod = types.ModuleType(modname) + mod.__file__ = str(self) + sys.modules[modname] = mod + try: + with open(str(self), "rb") as f: + exec(f.read(), mod.__dict__) + except BaseException: + del sys.modules[modname] + raise + return mod + + def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str: + """Return stdout text from executing a system child process, + where the 'self' path points to executable. + The process is directly invoked and not through a system shell. + """ + from subprocess import PIPE + from subprocess import Popen + + popen_opts.pop("stdout", None) + popen_opts.pop("stderr", None) + proc = Popen( + [str(self)] + [str(arg) for arg in argv], + **popen_opts, + stdout=PIPE, + stderr=PIPE, + ) + stdout: str | bytes + stdout, stderr = proc.communicate() + ret = proc.wait() + if isinstance(stdout, bytes): + stdout = stdout.decode(sys.getdefaultencoding()) + if ret != 0: + if isinstance(stderr, bytes): + stderr = stderr.decode(sys.getdefaultencoding()) + raise RuntimeError( + ret, + ret, + str(self), + stdout, + stderr, + ) + return stdout + + @classmethod + def sysfind(cls, name, checker=None, paths=None): + """Return a path object found by looking at the systems + underlying PATH specification. If the checker is not None + it will be invoked to filter matching paths. If a binary + cannot be found, None is returned + Note: This is probably not working on plain win32 systems + but may work on cygwin. + """ + if isabs(name): + p = local(name) + if p.check(file=1): + return p + else: + if paths is None: + if iswin32: + paths = os.environ["Path"].split(";") + if "" not in paths and "." not in paths: + paths.append(".") + try: + systemroot = os.environ["SYSTEMROOT"] + except KeyError: + pass + else: + paths = [ + path.replace("%SystemRoot%", systemroot) for path in paths + ] + else: + paths = os.environ["PATH"].split(":") + tryadd = [] + if iswin32: + tryadd += os.environ["PATHEXT"].split(os.pathsep) + tryadd.append("") + + for x in paths: + for addext in tryadd: + p = local(x).join(name, abs=True) + addext + try: + if p.check(file=1): + if checker: + if not checker(p): + continue + return p + except error.EACCES: + pass + return None + + @classmethod + def _gethomedir(cls): + try: + x = os.environ["HOME"] + except KeyError: + try: + x = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"] + except KeyError: + return None + return cls(x) + + # """ + # special class constructors for local filesystem paths + # """ + @classmethod + def get_temproot(cls): + """Return the system's temporary directory + (where tempfiles are usually created in) + """ + import tempfile + + return local(tempfile.gettempdir()) + + @classmethod + def mkdtemp(cls, rootdir=None): + """Return a Path object pointing to a fresh new temporary directory + (which we created ourselves). + """ + import tempfile + + if rootdir is None: + rootdir = cls.get_temproot() + path = error.checked_call(tempfile.mkdtemp, dir=str(rootdir)) + return cls(path) + + @classmethod + def make_numbered_dir( + cls, prefix="session-", rootdir=None, keep=3, lock_timeout=172800 + ): # two days + """Return unique directory with a number greater than the current + maximum one. The number is assumed to start directly after prefix. + if keep is true directories with a number less than (maxnum-keep) + will be removed. If .lock files are used (lock_timeout non-zero), + algorithm is multi-process safe. + """ + if rootdir is None: + rootdir = cls.get_temproot() + + nprefix = prefix.lower() + + def parse_num(path): + """Parse the number out of a path (if it matches the prefix)""" + nbasename = path.basename.lower() + if nbasename.startswith(nprefix): + try: + return int(nbasename[len(nprefix) :]) + except ValueError: + pass + + def create_lockfile(path): + """Exclusively create lockfile. Throws when failed""" + mypid = os.getpid() + lockfile = path.join(".lock") + if hasattr(lockfile, "mksymlinkto"): + lockfile.mksymlinkto(str(mypid)) + else: + fd = error.checked_call( + os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644 + ) + with os.fdopen(fd, "w") as f: + f.write(str(mypid)) + return lockfile + + def atexit_remove_lockfile(lockfile): + """Ensure lockfile is removed at process exit""" + mypid = os.getpid() + + def try_remove_lockfile(): + # in a fork() situation, only the last process should + # remove the .lock, otherwise the other processes run the + # risk of seeing their temporary dir disappear. For now + # we remove the .lock in the parent only (i.e. we assume + # that the children finish before the parent). + if os.getpid() != mypid: + return + try: + lockfile.remove() + except error.Error: + pass + + atexit.register(try_remove_lockfile) + + # compute the maximum number currently in use with the prefix + lastmax = None + while True: + maxnum = -1 + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None: + maxnum = max(maxnum, num) + + # make the new directory + try: + udir = rootdir.mkdir(prefix + str(maxnum + 1)) + if lock_timeout: + lockfile = create_lockfile(udir) + atexit_remove_lockfile(lockfile) + except (error.EEXIST, error.ENOENT, error.EBUSY): + # race condition (1): another thread/process created the dir + # in the meantime - try again + # race condition (2): another thread/process spuriously acquired + # lock treating empty directory as candidate + # for removal - try again + # race condition (3): another thread/process tried to create the lock at + # the same time (happened in Python 3.3 on Windows) + # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa + if lastmax == maxnum: + raise + lastmax = maxnum + continue + break + + def get_mtime(path): + """Read file modification time""" + try: + return path.lstat().mtime + except error.Error: + pass + + garbage_prefix = prefix + "garbage-" + + def is_garbage(path): + """Check if path denotes directory scheduled for removal""" + bn = path.basename + return bn.startswith(garbage_prefix) + + # prune old directories + udir_time = get_mtime(udir) + if keep and udir_time: + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None and num <= (maxnum - keep): + try: + # try acquiring lock to remove directory as exclusive user + if lock_timeout: + create_lockfile(path) + except (error.EEXIST, error.ENOENT, error.EBUSY): + path_time = get_mtime(path) + if not path_time: + # assume directory doesn't exist now + continue + if abs(udir_time - path_time) < lock_timeout: + # assume directory with lockfile exists + # and lock timeout hasn't expired yet + continue + + # path dir locked for exclusive use + # and scheduled for removal to avoid another thread/process + # treating it as a new directory or removal candidate + garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4())) + try: + path.rename(garbage_path) + garbage_path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + if is_garbage(path): + try: + path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + + # make link... + try: + username = os.environ["USER"] # linux, et al + except KeyError: + try: + username = os.environ["USERNAME"] # windows + except KeyError: + username = "current" + + src = str(udir) + dest = src[: src.rfind("-")] + "-" + username + try: + os.unlink(dest) + except OSError: + pass + try: + os.symlink(src, dest) + except (OSError, AttributeError, NotImplementedError): + pass + + return udir + + +def copymode(src, dest): + """Copy permission from src to dst.""" + import shutil + + shutil.copymode(src, dest) + + +def copystat(src, dest): + """Copy permission, last modification time, + last access time, and flags from src to dst.""" + import shutil + + shutil.copystat(str(src), str(dest)) + + +def copychunked(src, dest): + chunksize = 524288 # half a meg of bytes + fsrc = src.open("rb") + try: + fdest = dest.open("wb") + try: + while 1: + buf = fsrc.read(chunksize) + if not buf: + break + fdest.write(buf) + finally: + fdest.close() + finally: + fsrc.close() + + +def isimportable(name): + if name and (name[0].isalpha() or name[0] == "_"): + name = name.replace("_", "") + return not name or name.isalnum() + + +local = LocalPath diff --git a/.venv/Lib/site-packages/_pytest/_version.py b/.venv/Lib/site-packages/_pytest/_version.py new file mode 100644 index 0000000..80d836b --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/_version.py @@ -0,0 +1,21 @@ +# file generated by setuptools-scm +# don't change, don't track in version control + +__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"] + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple + from typing import Union + + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '8.3.5' +__version_tuple__ = version_tuple = (8, 3, 5) diff --git a/.venv/Lib/site-packages/_pytest/assertion/__init__.py b/.venv/Lib/site-packages/_pytest/assertion/__init__.py new file mode 100644 index 0000000..f2f1d02 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/assertion/__init__.py @@ -0,0 +1,192 @@ +# mypy: allow-untyped-defs +"""Support for presenting detailed information in failing assertions.""" + +from __future__ import annotations + +import sys +from typing import Any +from typing import Generator +from typing import TYPE_CHECKING + +from _pytest.assertion import rewrite +from _pytest.assertion import truncate +from _pytest.assertion import util +from _pytest.assertion.rewrite import assertstate_key +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.nodes import Item + + +if TYPE_CHECKING: + from _pytest.main import Session + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("debugconfig") + group.addoption( + "--assert", + action="store", + dest="assertmode", + choices=("rewrite", "plain"), + default="rewrite", + metavar="MODE", + help=( + "Control assertion debugging tools.\n" + "'plain' performs no assertion debugging.\n" + "'rewrite' (the default) rewrites assert statements in test modules" + " on import to provide assert expression information." + ), + ) + parser.addini( + "enable_assertion_pass_hook", + type="bool", + default=False, + help="Enables the pytest_assertion_pass hook. " + "Make sure to delete any previously generated pyc cache files.", + ) + Config._add_verbosity_ini( + parser, + Config.VERBOSITY_ASSERTIONS, + help=( + "Specify a verbosity level for assertions, overriding the main level. " + "Higher levels will provide more detailed explanation when an assertion fails." + ), + ) + + +def register_assert_rewrite(*names: str) -> None: + """Register one or more module names to be rewritten on import. + + This function will make sure that this module or all modules inside + the package will get their assert statements rewritten. + Thus you should make sure to call this before the module is + actually imported, usually in your __init__.py if you are a plugin + using a package. + + :param names: The module names to register. + """ + for name in names: + if not isinstance(name, str): + msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable] + raise TypeError(msg.format(repr(names))) + for hook in sys.meta_path: + if isinstance(hook, rewrite.AssertionRewritingHook): + importhook = hook + break + else: + # TODO(typing): Add a protocol for mark_rewrite() and use it + # for importhook and for PytestPluginManager.rewrite_hook. + importhook = DummyRewriteHook() # type: ignore + importhook.mark_rewrite(*names) + + +class DummyRewriteHook: + """A no-op import hook for when rewriting is disabled.""" + + def mark_rewrite(self, *names: str) -> None: + pass + + +class AssertionState: + """State for the assertion plugin.""" + + def __init__(self, config: Config, mode) -> None: + self.mode = mode + self.trace = config.trace.root.get("assertion") + self.hook: rewrite.AssertionRewritingHook | None = None + + +def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: + """Try to install the rewrite hook, raise SystemError if it fails.""" + config.stash[assertstate_key] = AssertionState(config, "rewrite") + config.stash[assertstate_key].hook = hook = rewrite.AssertionRewritingHook(config) + sys.meta_path.insert(0, hook) + config.stash[assertstate_key].trace("installed rewrite import hook") + + def undo() -> None: + hook = config.stash[assertstate_key].hook + if hook is not None and hook in sys.meta_path: + sys.meta_path.remove(hook) + + config.add_cleanup(undo) + return hook + + +def pytest_collection(session: Session) -> None: + # This hook is only called when test modules are collected + # so for example not in the managing process of pytest-xdist + # (which does not collect test modules). + assertstate = session.config.stash.get(assertstate_key, None) + if assertstate: + if assertstate.hook is not None: + assertstate.hook.set_session(session) + + +@hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: + """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks. + + The rewrite module will use util._reprcompare if it exists to use custom + reporting via the pytest_assertrepr_compare hook. This sets up this custom + comparison for the test. + """ + ihook = item.ihook + + def callbinrepr(op, left: object, right: object) -> str | None: + """Call the pytest_assertrepr_compare hook and prepare the result. + + This uses the first result from the hook and then ensures the + following: + * Overly verbose explanations are truncated unless configured otherwise + (eg. if running in verbose mode). + * Embedded newlines are escaped to help util.format_explanation() + later. + * If the rewrite mode is used embedded %-characters are replaced + to protect later % formatting. + + The result can be formatted by util.format_explanation() for + pretty printing. + """ + hook_result = ihook.pytest_assertrepr_compare( + config=item.config, op=op, left=left, right=right + ) + for new_expl in hook_result: + if new_expl: + new_expl = truncate.truncate_if_required(new_expl, item) + new_expl = [line.replace("\n", "\\n") for line in new_expl] + res = "\n~".join(new_expl) + if item.config.getvalue("assertmode") == "rewrite": + res = res.replace("%", "%%") + return res + return None + + saved_assert_hooks = util._reprcompare, util._assertion_pass + util._reprcompare = callbinrepr + util._config = item.config + + if ihook.pytest_assertion_pass.get_hookimpls(): + + def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None: + ihook.pytest_assertion_pass(item=item, lineno=lineno, orig=orig, expl=expl) + + util._assertion_pass = call_assertion_pass_hook + + try: + return (yield) + finally: + util._reprcompare, util._assertion_pass = saved_assert_hooks + util._config = None + + +def pytest_sessionfinish(session: Session) -> None: + assertstate = session.config.stash.get(assertstate_key, None) + if assertstate: + if assertstate.hook is not None: + assertstate.hook.set_session(None) + + +def pytest_assertrepr_compare( + config: Config, op: str, left: Any, right: Any +) -> list[str] | None: + return util.assertrepr_compare(config=config, op=op, left=left, right=right) diff --git a/.venv/Lib/site-packages/_pytest/assertion/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/assertion/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3de2d49bc9183f263b5e4511f1670087f6f2486b GIT binary patch literal 9471 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eff8b730775e69f15ad81bb703f62caadb61932d GIT binary patch literal 61811 zcmdSCd3amrc_)gM00@u(32@)QoggVvqIQXrEs>%&o0272wi!7L!~;s8xa9zpL>RQ? zOr8dG)QC*nk{Y`e)wnfPt7E$HTr1N~BWKdsU7i*YsDMU!=c;$yCDXajD3Lo!oI6kM z@BPk7@-)Xi_kMqom6fT(^Jd?Uv!4H`)BP9vAw5}wL;rx!(CKD$ zoUTX5={duoe#p>c(39IZXzVfKYZ^2SnS0ElUq(*`3$yfC*w@-)W8X~iYZt$c9tZQ! z>d9i??4E4)b@n*%H4nOma(Z%Do*8iG_T-BByL;R$o~OsdzIi=)?3>?{kFRC0V5qRC zkj1saUDQ*=+%~w2dy1Jm6Yi3p66UtUUD{L1+zzxSxk>Y3X;*f8Yl@iDh&uyLrVr-`}q;BM|| zX72pKbwe#ZEzDgo*gCYnXMmc4_Eh9UB7;1Ozs)3{;TGmz2wV1yE2UVJ;ya5yo2lEe8{7d z`vTX9?>_u)!ncb%#Wmx*pL?ELhwp*4IUwIJaxDlu$n|oq_#WcE#I48oaKJb)yQ-o1 z$!&oDzt!+>ga7Lq{u|-{+Zz6xf~KCMK^;PWM}4T;q7l-;LT+n>bh41|YlQ4$AwSRv>B6}EP{V&e{6Ez2KLGzf*YH0G z|G(7mKLr23((peV%=le>&somTJ%QO9#QL`kENaAi@@Ds2dcmJN%^mrwNrn>V7?d~) zDDf1^SX!I*Eu-MqGsN|A$56^J$8pE;9T_+J0+G$f0vGsTIN z3*)Ehs6TubU+d8^J|MX}{DXu3(}Mx{8x9_!(1YOsFZu}X@CZ--#{*v;3k>&B%&Zg7 z9o^U4abRD^;e*}#;gQ$5uWQfKPn_sIw(sb%-Y54w+k54MMSh75Bo!B4+qBK&g~!J5rlxzLH{u84)|v8k-8L7!+}2a*ocFvf8c=cj4q(- z(PQ&BjO%>{YWHwBI2;)4_8HUG-riyVP@uOro!Q$vgb^7ex1+cB%VYjQA>@Yc0cHJZ zxMRnur!k13Q^);7r#b_nbK#NEQ^$i`z$esV9P`PleD`>qZeoqkXD(bUYjggQh8LzMEmP}T@Lh|Pe;fh$-37M?>)QD7ReP3EXlGeAZ=oE3L>L1jgdz4P$^uU1J zoB?yz#CbR)zUDy2KsK^n^$fUHdEn^tifhcF~A{7_4ui1M&16LNsz&_DPXju~B4AJz5B zZL#6^fH+vPCmft!0#b-A=f>&qi}GVnkbcoDVNX~@fEDBlp%y8WGvSw2{$)N(wXjLQ zSxyYKCLb2pE`^Qjs&!$Y)WTDv;~hY{44mKR6f&MQk+ZdmAxZBeup~`T=KG+S&(DLBD*CbOyPDXVMvG0$8xYbVmO$U_~gM zdD1^P7T8BHGi?b80443}6}mhe7-ma4ls1L};dEB-&=|@QhXX5{z%fP@zS(tFw; z3eYkS^@c}!`_2aX&dE`WdWZb{xd7J-=p%Xd_W2Pho!={#+Dou2BqRjzGo($Sz+gY$ zh>i(SKX|>o7asyMXh68PH&UTJp<1K}9h8*t$xFJW{L+}|vi-inXxqG0?7g<`>bm*t zWbxKm=jFrqGjt8j2z4xN*cdY{TFaK}8^1k9u3`k0RgtS`sd>wMJGmN`cCc^VQe9Kb zbk|z_^H1`0uHv8S^vJoSZr&a*-W2Qn#^L)$czhblLGhFMdx~}M7w7J2HNM}X$9u*6 zrM9v8>xE^-CaHd--4+8X1w6uLT>U{(Mxs=nAEe8s)uxxD$a4r zfGUf=Dkx{YA-0c!$L>CPSs)HgS61cAs648y8-T~^hu4o&h&+Xr@BwvRqPMaHh<=&C zavG4AkCGCL+a%FDEPvC0ypWVw=rld(7=yIL^;8de`6}^{n?cHL!W=bUS{F6E+!Qr* z>YlGd9Wuf)+}4EFZY3wlg&Z?h7wSB`=aInj}%k+zWhwMK>CFkv<0nt zRP~@OY}GSRy2=ATr8@AwaJdvZ;FVm;cc5BvOD^qNN_`okQPph6gay4^E9Ihm(I*y+ z8s-ZytzA&NUy(b?#kj0~ZW<)|@VL%r?2Z)n2ZuQk|M2z!g9~t8fSny)fRv2E z5kD8;)5W2&(;|iv3ix>-N`oVP0&eK@@NFo9!JV`@M3__BDWDVh5f1d83ycG3A=Tix z49ZUXgM%Yu;j}r-`}+dGe^?ztqk+D(P1w%{gQs~~Ug-kz?d|7Bh8Xac3r=T@@WC^| z;dEv&6dXoTz$W>fl)^RyKnWy^A5XiEkBsqsfi6J(Cxn(5MnY*5LC$nW;9@WYi0f74xPi5vX|bmmoB*rmRz10`?Njfs!X^l-)K#^+G6G< zyEA34OW5n?!t+lr+BZ@(Yt|L}q&=SBG9RArk9#&RTDJfqyF6E3ntW+CJa;1LY>b)i z*`2ZW+0J-r^8>4{ylTp}T;QE~ar(tn!LCHXu3OyggUN#KDHHNbxT`<%6y3KV*8OZ< zR!-~*A-5MMUx+)aK6aPQa@WpXJ$G|^s%dwkX?LpWNTTV;qWkE_?!1|;(_3eDPw$?q zd9&$y)1tfOp1a^)PTow(bV+RQJ*PY6tWG$q=Q`)B<^zk)O(^VRXW^9>CtplCHz%B% zmmIDoM>aL7zA07Tm8kE!Z_~N9JkXh4nfG;OTP6dtE4Bsnod9DASk3_S-_UpSw3sE3 ze+2FoAU~^*56}b^CU&IeA;YsG)@90suow&x_3YYu$guvu3b0Y!g*2(>?z`gjXlN0SF54B9Fh^u2!TI|ea<*Qm0I<1hNyYf4rUn8fc>0~GZyQ@ z@(w`A4&bmkYKdT9oUn$8v|$h0Y(-fbyNd*wCu~vMr6(U!A5+xunnAszhAUM$YmS7p z2c5#anaD)$Cccd`1Dzo-$R3+6EbkXlW7HPSj9MjFPTWs*++oi{YL^8t?Igwrg^6Uj-OnZ;lmq%zTgZ#aGC+f%3)MtAPzl&VE>D5ZF61;$J zkB8E3Ze%#jaO^PW)c@`*>6SD5lNB|J@WS5wTq?5UiqN_uKzwvX(t<(#tF zU^1s6w)Z1<-cn)NlzGXOM;lwbeB;8wg`M%z#}-|W-^(kQd1m^V+2gaPr%t_bAd%M` z%UsGXm~BjC*TjsUWazRBruf;7bLKbg*X_xyW>n6e72C1o$hmTO^6+fq+_AZ?cwy6` zqj|Z!X@2j5={@^9_GEec62whT%d)3@c5LqSeA7Z`(X;bj z!@8-?yPn#6LT2&e?YF9KdE$k;7afl+TOF}uQ~5I$(-n8El|RR}=xbq-KMfIV{6RzZ zKA-MSeYSmV#y$~wgSSpcy9Go3f9(;-|I4oe#A=}aq1Z}_2C-PCD_>d!0+8~v03^n+ zOmjz#5;)Z&6%?Sa1?r`gjL^^k`U5hMQfj8kQG-!4Aa4Llil~V*Fma5yA#g_ZFTL(E z8VV4lLFjNMRSm^l&{rBYJ+A{HWL9l5rf=vpx0DPyM*v~1%>+HX0X>}Y8SN}n+9$a{ z^oT9wAu>X3lx-&?YL>ulNPiiEtZ_5itQ(NG4=E2Fac*RCjM@}6XptwJWi@dJPNdZj z@tSUrw;w!1?*;!jF@*sry}-%6BmE*kCQvPavQSHArUZw;Zv+#C!p{0bUZBnwhP~ml z{xDtvMm|tpM}c5>fvgDdEi!iNZ2+219Lte@IWtP5L?9#|*#tx0J}@vtJ}+E;?~s20 zJ3?r18NGoR+47bSc6F_-t=z ztiL~ak#8m+3wAL^pwLJo^QfN>hlFB{s3OC0 z#UW?g4*wWZM%Fz9N7g3q{s>7zkU;gS=rG2tJZm*v2Of~Pn)4YGFu_e~I=%`=v z*1YMs?nrreEP8iLIRN2{N@t%)7Wz_!t%<_ccMb1lypxeE+&X2x=klan>l3c^Dc7ci zYty^O-h1YqXHuK@B{uK79r$qX`@!Vqqw%MXB{mpF4YuB+UMazS-uPG`<;{Fal5 zCf8K%{8ZgRvKmcXfufzkYyb3iPHv+zB zkO41m*Y?+9At~Hmr5gS0d+tvPK4Fwe}=E_DykT2E!zyJ7SmE#Kb=?QqWq-n*dN?2e8~0 zCQ|}}OFU)f>utC|KogvUV1Ez+5Xtooae$@)-wtmicWr#&lms3`cp!0%1;n)l0Rs0U z7$&H~6SItg3I-i`Y79@x1-=Z`;#aUzOaCxi{QNlLgb2h43tm|7hw(d7_ptT8l4bIL zQwHzhbxHTJJ?qNW$*n1SNy1(-yEkdCnA>&7zFx%6;>8;m_AYFR7w%Ye>;!oaUW5n~ zuqduXC!;B6UBX%SMs#6svVQBLbDNAHKJnqP54+-}#}{2EmI_KfvFh9O(jpDz0iDrU zq*3Sv5p60hAx!2nZ0}l#j{(~|#2-;3-lpF&ODRAP657H_UHOXYi9*-wo@1y5m+`1r zh?AkB@Ywbj@krBDkWmN|J7V!Ru>Cu&_nqkSnfXm9ByEKdmB~tYU`m**plYzq&^{*Y zN+#9BWRy~dkY|Ycj%K4U7ikhdeDBfR^?rsVp=rFZ_ZTv5MVKZj_lAUf!$M8My*2LG z`txOH!B2HogcO&jiklaUo2N1mX?Dvuch8-8v-f)M!pU2G@0_|@yLZV`G;?_RaJ-6O z_}N=$Zuj5r`S8ne?^BDOV@o?9o9aw?+7h0&g-r?1wsc7aI8`OgKPXwEYB@hpoANmJ>C2R|F zSX36sX^HE?^4!zn`ABm_Yd&E%$lM=R1NM?qfQw|n9LBT)Ve!Z^q6~EhpND88_6*3N zUGf2tH)#QOQIkq;A+Nu@Lj~kbT2cWOLNBix32Y;M586dbb=9MgL?A8(*%qL|H5$-D z;pN(~wJu8ylmZ4S6}RMiOLSnvG5Cy;-+mHOK1qN}h#ZM*y~6=;Cm1@q0$Ww#;3t4K zQ*=(m&%Hp9MUg&ba3L@_NMHZ(I5G=CHA2GUy%&OmgP8)D_pZpDwjzP^t{}c?UAHfX zUynMcfh_{34uPcu1q%K-^0J7uK{}HQ_LE))G2n?Mlg?SSYe0bD#v&Evpx}RmNF{_z zTfjLP>Fe#~0TTfndcoT#X#z+^}|WW`jlg5!m;yKXR7_FMEg@o$FciHovr9W zhAyu**14RUH`6rTG|MG&t79F@?wXW)Tf&X~&P@!FJFd!E{-2EoPxi<8-Z}T1<=4yK z^(=7TDZZQE4pvOc)s%2G&3E2)Z2*>=R|{xk%Su^`6V~FCwK8F?oU0|ltJRUR)+MZU zDQk1W+I-jA@(6DADKrIIs-&T+8`4jJEX9hES2zO2M{Bn-09nHhv_gi+nQqF2EM_|) zPK<)Op-&edWjZH9Cm$2kO`gF(qnjh^pJaM4UUBHeDTxUj8Ub-h8c8c=Etrdh3PU4@ zwwAtggMssbL7yp|C+fAd&;&xhy)>Uf{BI(zAgC$|hC*Y3UO~v;8xBGZM~vz-s`eyG z5F)%l-im%5KOVjnY0g*ZdFVnLk?xZ2|Fm}AvpPsfld`T$Sl4~zs9Vb2dwbLEy147$ z1EV1;8&ZYBB5WbHmVefkbI9_Ae&h&3LA4`5Nkha!HtPsdYwZjY1nM(DX#)SG6ncim zY=FG+0>=J1;0FYwL>zP0UV zMqPGQDy#BNR^_Km1o!K%dgGhs^_ClkAJgFdn07RXfxb;{+K<>w#H8_@;QgdAM&RgJ zK;XQFR!EiP-hLIyK8q{^;f4O^Eu@YzXc#imDuRX&{W1x^Xr+XStIHlOMjKY02GdHw zGjGaFE@?joL1IS73oWC3rIuxq$nzM+Apm7LMm<2H$-uTlRFMNeqJJcvBTHLbXw=Xb z{0(H2E>Zi79V5kn)RtCL+Ibw21_Rx+Z(;Wlfd7i+Ls_jHbT^GDFLpH?RAi*_A@Eis zZWB#~t;qJ0ZrPcea#kdq6?64>oWB3;+V*#@y?^U2dp(dU-M)^DGwZyyseX@2C}QFsp5%8tA!Yn4^rHE| zZ>QH@dUfHI&O9o!Ao*kDyN6y+)9W0)h@HhBrWX+x{I}>uu${k7FIp-5@8gv&Q2JV$ zwxJgP={}z7lg?5?$?t7SMyMd0W(>99>*y!F9>-tkPx1Py?jzIA2c~Ru^8>HJ+zg5| zwDoAJf|;B2~YA(vH`)4;+I zF|+l7o%}ex(d=3BwB0x2d(Y;5V8-`BW`nuv!3ljWoZWi2dE0|VySeB=vCG{0pvG#> zeBd#fOOc7yp0X4qECsV$?^vpq?Aam_f8mb3?ms&^|IV@F6H|um89lgN)@q@J)Glpd z#}~0snWhuIs~?3{*2FL}R>xQ9V3Klzde#kSZAMEJue)`G1`;MiJK)CY=; z(0vO=0_hAa$FaUJX~^`Q1BUPCLudViX)`xAG#c_{K*wd2AE1nbl&86W(0?YBHVc`U zD2LT-B=Egud1)^ae~5IO&;g+dIGCD?+48jmFCV!4+?6j*eleBRkjQFSH2Lm1DlY9? zGG$Ou?CGx_`Pku{g8EBM!c~)W)Xrxn9Bl}<=cnxD348fmal+nosq>x?|MJNzUzq&D zY~GxCE)4PDqG_F&(dB2ZJU{vTY+Ev`{I01&z=fp_L~Rs7-7Bad=B^PZeDn%_r6rzw zi_+ntQ$Q0Y$mag_Vvk#@ zbt|#9%asDw2A-76)CEtfX@WRCgd?naXr`j2rf7!N8bHfrtb_1E4g#+iGsexr_kv#H z4y`J!@)4y`ze1nm^XBZcb~C=+*y0+z?oS8nmsq)w&-YK`0Ra$1u?9dS28Cvr?$-=U$j=<&xR1?XJtTk<5`uz{MerR zQ=P$_Cu@DJUnpG2ywz}f!v|a6-+J2@_dKy^eRA39`Q?2xqW$s{sFb;XszbS|6z&D{ z!m(TWg;3nHbJ5xkzN#zlm-p?6^l7LQEqKkbr&9N3_MSZBzqi8mC;58|b?@hy_mo@S zFSfwr{Yu;3JmdR~m3v*r@4L)!f4|VyVK9EbvS9BnPzzCLQ?UO7T^jwx01@FYy?`{0F-D%O+9_Nkf$e8Y&D=V#rq72r(`b;e`Ter^IIQvm+{@w54xkXcW0) zVl(uu5e4vzcmdioT@%u1-Ajq+MatEo9ynxH|5V%8cV6mT%5oBP z{d(`Eu4Mpg!`Js;+P7@azOsFC`_##~z@okRQs=VCJ=Hl|H&;8Kzi3*&Y;#R{rp9Lb z7d9>0wq4o_g~qAe*tSbY1jvkXRy=^0dbZz@ZXbUFVS;v_KCQnPqR5iEd;6e&=rrfw z&HrEUJV(`~QM#nN%?TNcfERu`9RWy|aR%bdtN^`Hy+Td{uFonB+dc^f*rTPQuAsLQc9k-r zJzOQHs*zmESL%h3dOqZSR$ZcYZT66YxC5auG7*wNg#dv0Av_56PFPhl&>Xd{+3pP! z))%d7=OV}aS0ENLbA3&|IsZHPu0)$;W|un>Iy!=bf~TRyNPyPs2b#eoxTKv3j$zmj z)d}$D1wcz`i==5NvgeO6Zo;ZCZ?GRaD1Ci6F4R9ZNZfkjDv&Q2ngYL_e;r-d{bLv2 zkxa%IA!MT=VuxT@L|43#V*CgWpZ5<2IfnW)ghxh51&0s&eoR0vQn@<9Y1DtneQB8>W z?~#htxdey%MtD;G^b$efi{vAMNJz90gI_#w8u7rN__==8ed1o|a|?LdUs5dzePcTn zPskMiC-nLry?z_7v=JMoLg4U!On#Z{oPe-1exE!{RASn;?_ytoF)*2^f&T*vvUXt9 zh!f*;@M(DPgy->()9VL#K@$^uE%B;w=#~E=d4E9OCgOVXadKud!iTXz(>7`>a|%1C z0}+^jL3|0!fG<)u^df>X)QANG6fqklI-!EqQ{k`o#tc8r&}BOA8!WcM<>Jz7^;hfX z>RxM&9bWbn%-U0h&56S1q-R}h-;%pL<*rM(>tdZtg1$ZE;X7lUpJeJB&RF>C+h=oe zc;F*P&eWmV^Rs8;b&tn$_bfX0LdN;CB9suzy_836WjLcxNriXEm6>R$KIw& zwLhMFV9{}qQdJ14yp&2mRT+?|V#c1pfaFQf-9@5=qze)=_;`}0rC42Ra6Js&yUII(8-{PGi* z&ILiT!B!||Ghkv64S$+f5BCqvl`M_!< zx81gXllcQr_WoA$2URSz)l5E{Sm;*s0iC6ffR6MKX2fu*b~x0SJ#b?Pha`emODLh> zO~6KQPC8O6iiH@eEFiL8T8@DsoSzN>29iLtg*e|a6qYvfW5eJWeiLrCl+$(u({XC1 z7DEUx!vSe23hR&#)ARItjn*OUyG)E{&YwCrw`G1y($tnRZAqB6EQIcwcCN)osoAWl zpTM!AsVY4XE#8*29rF&*;u?Ie<2r?4QK^*@Fj@6T=-vcQk%UTA#EDG&XsP6qd{3$a z%96QGI4h>VDRUJ*JEhO$e-k~oA1b4MFP+54hBwNS8l-<%gyc+*Ul=+Fj#gV45cEk& zj;%z2E+#Zfm?mZe9o}ydJk&?;8+<;L^yM)eE(m4Pa1IA9koFYQ`-Ey|Ak^m{C21ix zp#V*uajzc=o~R?KTtRx(ch-*+XQUoV#|$Jb&u%Jt5c|DcnU;rEz{`zLaipbs8rrgy zjh|Je4`+R$zsfoy7$)7-Q2~uWMIgKs@CJz4NdcD4YpG5`rCA;!(JVr!8BGzYj9S&7 z8h)Xk5s+|Az=b9a2G0c$gq+drQFI3u2G#}3<oY$!oEp9X69_~8 zi)>fURBeQrPclVX+6sl!6h*xxc@d+sBgm4m1>s1ZMUaAH zIFi|$FYW)xp1bUr&2^gcC>H7M@NzcgC$dm+kJUj=6$GyDw$mkg!9;=axBX z-yJvYUb}L!YK4{iA{@k@zGMW#sUNO*2#;4rX*E|f8bS}PL6BXi&=2}?a+{dj7M52y zXN~|oV#Ucmq)He$L<{IkVn=_8<_M0QkMZnaC7W4Nzj;aXkEsii#`KvWtT~9WB_4tx zUH>uF_s3K%{tdh!31{bDAo4ps0>cmEJP~+l&&hqq_8vKY@WgYydyXI9ckIN$Bi+Y& z;(Y+160}U)zWm2hAY@o)E-Nj5=W)OVSt;p&)72 z5x97wLbHHlum&6bFUZd-GHgT8KG=Z2wv~H)I)@+^Hk4kWbB97_cpJ+4 zAx&N4WC8Qe*X5MY;w)>^r31_Mys5s|H!j*Mmb3GxLW`xF-{syLd}lCO+L6rej2V}# zt}D(-=WO<(wf?{47Eflx46(gS**RCvO`Zee_H;74a?%I|A9o2PbPngGLu+TX35Bv* zP0Xv6Si@VW6g>n#zz85H#hxgbHPRe%2zsWJ3wvY|+$fRYkO*}#2!4XB%g|A7Qfx?D za8LvLD$oBT(m@-5dQZ(UD^w0ZkMg}|21ic&2bmcQJ`2@arvBQ9B#^jUoKwcHPAoY+ z_f15zxQlTND>LbCLJ!8RO+R0@dtlZ{Az zU8KV@FcKUVCYmwrP?`OKpbJWDF#CZ6c(N^^mD&y2(v%~?`N$~mL6(0{{YS!S7>dZv zo6WmZ5^-NM`jv5w9vrGE2gG1sR10=(OJB42prU~;z1~i?`IG<~YTA*{BE!bjs zGz5LQUcp(l7@bkwOQs^!lNjpK7m`)@&@YKBn6O5zFXPPZaotN9OrKibDKXEPZNv!+ zjJiA_&vj$>3I2Z~14t!}k>F_B%=?GW1o*#$7n|q;s^S0vKgtsa97vPT1_Wgk9C3lD z$AHpEhSJsm&Y|`Kl%@4S&=OE>!O70iL6G{u)?f*SA$B1WDHAru9!5q0#LyyB7r|Fy zxc`ZUd>YY#;5cxY<@J4URL}K&yWzDX^Uo{4@oL8>dWUUZrrUpm+*)RY3MT zTp%GKT`$@Nl|m@*Sqc-D!nmboIVUfbQ>wvG4x31 zc64wIhABX%l6n+TtZb_nn24*|%gi=q(v$*CJHkU7it)sN)}IML?+6sHR|RdnW5dG% z;v)L_Aol-;q2QUbOd%3+0ytcEKHzIP(Z!REtyO9$=`zv6P_Tbh4M?qakZ(j16OhLc z&>EVy89Gm4%3XiQT_4*NH?_?_jTh^%NCxrjYC{p@i}YGYucq2iS|3g8znIq7MGWoj z5yS4?X?;^#zb>tBOzT@ChSpXFUL%HGyCR0lU1>eywBxfIQFnc6XlzUyf}uzTXeQ(i z_pne5tb6DmU!f0AP_UXLK|CCXF=B+n|E7LMuUAXWWvrcz9(yUf1|S|z#sHh7nJ$FM zy4?G)3%!8Z`kRIs!>^gm$p3PI2`q`Ny33Y7Fv66SA+qOKpl=LD=AdXg?i~*X207Fj zvOE&_0T_U$#PP+m!9flR(KM(W7>{SyF=R*>V6ZKW{Etv@+67Z=JjMl)dnu1lIt%tt zNIx&Ye$#G5{%JFdvjP?0L1Rp>dK$G2$nGC?KL<9Rnf;YR%Q>D@PHiHmc8>c|4jsaF zd*||!?)sFqK4Go@iBXr^ykxBxcIU4&ZPovQesjjZvu@Qhc*+w`V@03K*!ZuZXTFyC zHT%~bU(5Ph_KYs5dj+;iHjMKGv@;mCjva2og1yG5h%&VHEIOOR zTOsB(F_$rFekJpA!(`?q{pF?=X*D=AEJY^LnsL-PW75uLL_klxVjnL3Tq$AqfwLf` znWe;Xe8oQL2-QtGw1k1MQ33tZVwvZo*G*5y{fGFTj6G(#QA+1M^GmQK6A5(?`Tl0PW33EQMYh>atP5D?NY zVkHjOh^0DI;TfuM&%^o#`5>ND2H5}x8e!@G9dTIS-oKu81Alz)lsS4XOA560UKg63tNso(k@Dx=z#R-D9NMP@v9ev-E)Vx zO5PxaNHfZ34!%c_YMrY79or*0~(N$>;p52oPw4fBj{QYToQ;q+Qq_@sP1Q;VT1 z^|DqNC>n9pCykRj8ez`(iXJe;!0BF5X;4XdykcUyZv0mfGp#=*XgC{r7cznJF44uK z?2L-foX!&3+$&-bW(IrHX2u~T#15(xp#W_3F%NbIJtb@S{NTp z?nh!o@}ZV?GJQ1|+TF4`{x*Wdhfzy{!_LyUvmtJ6zezJx~&IN`^rOyx?Nn7@k0Q2AxqM7~dxGB_~ z_dK~TZ8&n2kZT=J+bP4H)0Xai&pdIkdmn^m-JMVFId*v8F`pC1)A>H0^qOD>i*g3h z9Uc|LW_8SWq`x0sme%9!4)tz2NAQ4_c`rt22xpb}Jyg(MdUepNlU@+B=t9Ii0R|*K zkJBRziAQSyc(P^ELVf)%9RDxK_zYc1*`76?{KX$zr zM^9&SrZcC_OF8aWFV4I${lXof?dKQruD>J(O_~4JT}UTX9n+546LWQQ^|Q|}x|)_; z#WUH{*|X>Gy6W!b#?zxL*dlK&Yn;i@0w+`K^ z{QVV8V<)jwM*XGxzpEe@y7OeQ+wRIYii$Y z)i)2`%PX0UB=US>=zRVBmbkY~2t!Ba70)~|{luJS?#$fzxw1u1>qjo{k6cw~&Fj`w zVMC&@;budsu|0uIVJ<9Zmk|6nsPLBg;$+E2xnYY)BsJ+ruDbiXbZy)2Kdx7?Mlk5| zBO(O)2Y(N*uK}r(tmcw|SjlLs^yLa;FiZ}ku?`rGEs~7J!gQ-iWjxse)2(J0Q?IYsB75w-pI2BCLK=K8`JzF3L z0|$v6LD8!E(@SWEp1b)vLOacbO?4b zpb>eV?0K-Gfmq-{qE?`sFh<81DM#SG(3K#2f-(#hzcVBi8VGGV1eZ1f0!PO@e~oyqzUf*R^b@H5evklc&RmM-fh!qcg7 zlqEy*(G9HjD`k=^yPUd!szS^KHWb1rD?JNRfn`G751N4u;CUD`3`;`@@s!t#S`K?# zn18R31A@b_U= zmc~*pM3f^636=S!rjbPR&k@0E#T+7=?2P)8=%0!dwx~ElJ#Ybdr=2TYOYJ)5D=D!HyHF`$&)`V0LwmkywE>gLOO`25RRy zkgY_%hH#>i$DEK%Y(mo-`#B_^fzS>?h`U1`y3h{q9zY=~n(CtX!xj(OrDkhM8h(l; z3@a-bP)Uw20=pgO!R8cvG9T^=P#8TvN#tN%Kj36x-p^HRMDO)CtKx`^d zE&>t@K|Vs><8-u?jR|C_z+z~en1YrlK4B1(Ut&N~MA_L&vU2e6Uadlno7)>wdm zA(~TFkB^Rv6MTJ@R9~76Ffun3W}W+}0VA=fdS1ZWDB`;i?RN6biyE-Vj}#q{{4jIh!O>`{b= zFKcxNhz9+cp+Pdp!qW_f=|bqPK_8noE}CVkg#%tA?;mRLNhLBkRJ)FPTGU)%61lS@ zgLITqT8Ke_Vp55dmZFqefh-Ijqk**IsQRbJz!Rb|UIk+%@t}N0Us@N^ByYh2k%SPW zascB|v|T_;fO5o^EcU9@3z%$u=cv)lAweK@il@ZFA%=gD3MA_tsVOa^W1+Ka z3#L6#yGjB?00RH030Ur>JAh5xdQ%OC< zrZLn{AfynGB)0ouyyfk!MSyZ=nXD659BAYB%QeFikg@_12SWY~jQ}f|+I0b4G6s|Y zIvjp%Ig)-2)(wLv*glvFB%57w-IT^Qg8|JD2r>c(^b(ywwj&5_pu0m@$ueLPb|I8b z!z;}^s)R6V;;ttupRCXkBRn5}`Ul^K<2Uqb9{VJ8)qfqVI*IHjF}vELKg8*}UP{j% z7#=Tph(R2gnTz-`i+30r?&{wVM(KKG5fhF;sqQ9FwCWXo__si6(t~)i5&>gNq>FKE z1AEje45F8=W9Sl2DA;kpVhrOVoKwAzT!iDtP2@U-j0K%FGngV9uKGZvFqU~8~KBgHbn6cy`u+35eA0hF{KX~4Ar+uxQwX-QZM zVap%J`N@8Np?DR5C}3*Y^!Ct=p(R%{tQE9o$2#vins5-c6(;-_9ZgcyM!Fs#^J7QB z>!zfmjLcqS-L;B(9w<(JtZ=dEun37JIQg3PkYNE`#W99<4FoTeM2YykoPLZfDj8nR zg$|EA;Bpx%Lt-Rd=fDZ(^f+{(Sr%jicfg6n;UE4RiUvv4{AdZNB%$ZS>NHx1I#l_P zn!~C`&gbZ7<)fLmOUh2prx_ODH8|8-eh)ifrJOxwrYfP^G+`Xxsji=@O}SDHl&@Tu z3T-R+(3>J&y9=Qj99GWELiNMY3K)h}e1VrfVY<9%Qfc_C7@tC=Y{@0pp$?@KOTNmN zFV^-K%#Ugv#+b+$b|6iul85AiInZ*+J>ZpG%9pb+-LQ=+nR%5GD!Di-X9H*7cr)`Y zf(Lj$Bfgz;tc(wRDa&y4XQVd)vWGNYMO zdj2n>nFBt_U-?QK2#(2+o&CHLRu-+oB1 zY?4Y*zS4+?n6-M<2yap1NUo?|svTe`^aPo-%`uUsDlIF#O^Pk$%)fRP!9QA|MDTJI;C=yuiT6J z%m46@+R7t^`uqaAjhQ^jx&C|{du$(|bsvs#UC{ZmBb&Ptp_%U7BnlIh6q)GW zv=3aNw3%@`=_C=mfFvxO#?3s)Zsh8MHDk!0$RZW9!jiUkjSVyVX`O-obT$Y3^bA#p zGz#sc+N#Wsm~qC-A4+EkS@C2|12*DEM$@i6q4D89bxzg}GL}qvni<8z1UgVnI^J;v z_v%52gDdn%ijy{iDU-G{odd>Ff|?T*owfyVHjFVi(-{JuNOz;)TpNc55(kSex?;V+ z^rCp!gd+gJj$5RS7^buf1S1Z7ku^7A$f&N!9%T&)<6vR!fe9|2T4Din8^_F`fW;VJ z{7!286EvJ=RstU71@RNw5*c~dfb9rQX&%I9ikp+;T^gD0`aX2XI+=$3J@qJ7Dc zyOixj9H%>GTJjXk9C-CWtn+VOUNRaVe&hI?&t89a(Zfv0he@DxbK~3XH`*6H+gLdH zq$*ky6|L{O7d@Nrk|2OQ${FdrQA&kchh{wqI>;Ep5mqIhJ?%eF^(o} zdV?8!TXd~o@)XV-nm#mF`OPEbgb~WQ_B)=|doIu4y7HEA%zDG*fys{h^}4*WPnva( z{3-7Bj%!D*9$B8nc}=OjwnSdrWY@jCA`)B0x;|cE z<>g~opR-Nda6)VN*2(zMrxsnumIbdRPwSG$yHrxSRN?)^X3ojIZ?+a>PwxK&DzqLc zP13blj2GGbm*zud6zq`9GW~dYo59=cm7u8 zj~wkE))STBy01eFh@r}0>Hms#(mLgyIv%$c!3RomCyA(7YHFFUjhWNgZdh)f**?9U z&b*Q7*BbO%#r2AJt5a=ViMFo4H2$^Y&m3{@(~HiN%VkyZ>MgM9*>k-Ijs;JmdP}lw z>y+u$Oi^C7Sk|)WYQ;D&7Bt);iEZVzo~u3c#`)aYULjiHGF^8A+kX46?_YE`#~saa zck^;Z)wP$dzBC{HQN^Zd`;=)aK(PXEoLF=>F8P|KI+knJ%?Ex|v+1gJRzJIWxukY3 zaP#=vJvVw5OSauBt+;mR>Y=&to0030h1z8Ow!5Xu{GjF`>Y5th*F?gfz%`?}}-1W48kSnX78@t)} z_SqX}@0M=82NR|w*UP)>slQ*V^KSlUx6bML#ZPn4-OOam2gTkawWbwnKSZc8;*1Dh zrk<-iPYUdY?zH>pb0_v4KhfKHq?>N(eCF7}6Z^!|_&nvI(k|u+a7Jp^E>I1FkWLgA zBHephur>2G;YJEUPY=1ukF1a+9O6(LX;D;*u+f|36tY9w-lZ=oq~Q35=3y*N^Qba+ zf%p^D8xgl~3W}H|620CMp$$c334mfJy>_T_0jQ>3QXGj|6}G5;#$UN`TEln$-Mnz|GI%mA37Xq>5=1rRQIuXo5Lk z0Wmb;@;zwoW4<$Eq9#JhWmczGzy;|ruhz^Ks!~Hkw;$!gat(~{HcU6nw%>HWU3jDL zt>R?vrkDkDmgf8HGjrU{j<*loIPlgXn1_#<$p7Nx#aYwq+}x(u2Ii}hj~H$?ecwPS z>SG!ID#LA14UWvYmWG1{^Dx#^@9j&s?0oIF~KMjDnpphcr5;<~IQ+ zDtxE#JH<(7d)(Ttz3kXHkX9REE=te+FX0F1S#1mlZv9lOiD|SB_})NWyQfs6}c zWF{&S!80L4f*u2;ewrdeS;py28*tHvp|&An@P#lD37g`-fga|s;FZot^`xuWRgzq3 zZoW)$Y>E_f#l*}}r)9F;HO9Yjh0&Twtu$G8qZ+`nfQ1*mzVY?&TxY6gd!lB0vSLTF zWM{&&6GvY+Y^Dp+suR}gxr4YI4LH_RkPOz=;Es#9wN9A1sGs~$rX$lSf|y$#Xh|kd@>4-r3BjQ< z77$62tp?SlN=!3BCDCNu6RB7%4^j9HLOfXr6ZbF}l5^0F&)pBY{0S=3n zRpRuxoLI94awr`OnWxDLb8HaG3&en)5d$iED(%*gv}@5<8qs!QYK3YrAuw${x^~b3 zg!qFLfQiABwJ2c)6`d;HoG9LW*ScjbSe0pkLgd4+Gi7ecE-j633D00MCOM|!;62(C zmSeZ8UC69qh#VxrMvvVMabputBzw*^erD`k9##N6hl*oWp zPt>9chvabeBlVQj^CWkY+)^)q_EUQMv&v{(U4o*bAmw`;+-}D4#z}0S(cgnk?oy8U ztAT?EGMLte_V&37?5_T zOahnm5OP<$PK9tl-IPK8hblNpI$Wf(CuakI#X@|myma_#8(LkT=h()N&Kr+9wT9X zlUzi{z>Wd&ZopBOh7Hy?1%;!u5tZWKrs~+myqS)NiGB!#I#FC~xbPD2=G?V=(wb|k zfuF36b$}qf1%)vL1vg|hPB+f9Ot&O+t8il$F5TMk$_`i-f4lKU z4@}_FuJkWuIEX}A-?g(>&%QR0WEMt#@l&U+{)C?KIN#4I?XsA(tm?WaBEg5Gk{cdL1tbO=cpPa(Jxd7r}fC zjv)v8dYK?-%}pJcd@CZ59f42>$^#Y0c=E(q^B7!{X zQd%5R)nEWh=$vy*u#FwhzxRQl0uZPR2J*0b&9{_(jE<|=b3^5Ojf9l3%5uqm9Hvvz2Ymmq#7u< zMl!SI8m*l&@c)2TY?M+eUsVYj;&bS?-2aeZ&j#Po8-Jy@rV(!>XCcbBB?IGlCG?vH=t2& z)T#=9RF0W)j&@ZFqR3Z0oI3pRRe|^^)lu$EGR&tn3Jxf^h_C@lElMl)uFVt58d+y> zpjED8wkm$7M*M6Tmy+^!LLtT#bqx@sa@B*HX(7DgxMCH#1`e)Df}c^RH1cenX}8TH zm!A_pEaidnNC>s+k#f_9LPUp5+A`(Hsd% zN#`{qP~}QJX0K{5h&>t2J*jqQwdfP|DVZ=6n^()modM1NukJ zjGQ+Q7prQd)qgVFraeGPrAqU^0g+X?Xq2ql1%V*aGeJxSjoma_BJ~twPiXas155`A zTB>1&#!UT}Lw{8}{?#FSv{-Z9u7uCULjS1W{J%k8Y54#DcGZ@C{xudYmF5G3Y8tQ* z>ZZV^Cd$IkNPQM9lhPmz?4617@N;rlIp+i%5oh7cFae?(@g6Cb@{N`&!e%HsHkfpm zx3OztHEJTWfeJ223?aCDE`JNQXqA|4)!9gJN>-kUlum!jOpz5lA#kKctwrICsn$_k z;>n1|a5E*2!oyB!OJzUgIIeJVPtJw^Up$fv#(YRPWlZ&1DW9j4N9Cn6g+ zs6dC5k=kkV2|9fynA9)@2BGU{6)zYK@mHxPze%tAlq!qe8cMkb`+}k-=(kWsehRO& zjTAOv=O1?J%E1F@iTb& zDg^$Q*q6QhpHc|56qc=UFcAz&F34^Wc2ic?;lLSY-GKiK`e_qJ09AzAEm;u=^JyoJ zO{on)r!C|`QLN-(x&X%hp>W!W^KCE(Lv`;>JLRKlq27L2Ai(uHLt(*ATs@VPO{cR2 zGje2fPW(+47kX&~SEOeVY-DtSg)vs0FftBy>99CE_#kT$GD9p55 zLlyWfEyag?1)iUSaazQq{*iKdGRm5*>hROVZ}|XpKhAEsNCEWB%;5AOSa1igA6&Gz zyt{GX!h6woqN%M15?c=>Hy-@(sXH6H|He`Bca8(lsU#C`)d_cX+}#>?wBo(w@T447 z2}jjOuEr(LVd!f@(KAC=T)tFPmMZckihN52<#ba3j1*>7f1+}(>f?-I+tp2 zT77tIx^VTvYvc3llJ0FWtB`LJ%){LLRIV?P>$_R~ zcJqzqg%^^I9m(9zSO@CuD!B5(D=#dSdaoV6dN|&&^VT!p`@(m>kSskoWm_t)o3?xm z4a_I5J~5x0ENv#Q>M6@|dF8d>)!EN6V%D!7~Y<+9{!bmc|E4F`GP+Ew4*S}l+Uc);L-)T%1?1~*g_2`=Cc=<+Tmb(i# zJ&UR*+vfV_o}S;E%x#5VUg6Ah)6X%BpLxDm*HXDRc4Wy>B8a(~v zny!{!@-*Tq38oENK=(=~J?o|4o}{O4$y54(^m`u|E#=vb z?W-nFdHsA-qI~m|ohHKg^!Qv6bcpuf*#FkScSE!UZy!!>J(?_dicJY&#?Ch+b2r91 zR8!=5GIu+sgZ5PVJTvDWT6TEy@NCASqXH8IzjnW}duh+U56Zt^o-922e)%oW>+WlX zR}1HAFvV>*Y;W1`7Hzs+9y=H>JWA65+UB$4;!+AH8;Ki>t47rQHHRBIeD~0mM_xUG zxm8#TBZGxaQzo(oylJj*epAxD9v7KoZ1M_Ip4x;55$5;b^=$fyO;^;6!ON?;@6fqC zS6-TYDQ+$Q#ZM3GF_gdf=@wnlQ~D4wE&s~b`dp^zN2b>2oGZEY#A64*(?7@n{;%f$ zYH@9WW6E3_q!S+;9R-}G=M4Tycp&)mdMzHYmTKw>wL1;YFo~grM@uzT5jv~lX&nU> z^MLr$a7H=qiZjw$VrOU|$tscHh$=OZxwBH8aF9?5^(elQOQPNB^3pT1>1iBlR8r+j zag}eBu9hrPeoHPz0-{PU^QEO&;0Ve0lgT3jx)*uU%^a&9MdVB}ksHQt7>s2G*_gFz zU$!9WAXJ9`U3xu<^e}?Pr7azT6<`4qG8`Och)_CXFK{bhN`@mi&vUSUA0#Uz7hw_- zQJyDiAflL1+J?)2=yn8Tm=PRiUKt#eaYuRn`;_cuyx1M&(k3tL>P}(TCRry(MMNqe zwz11m{)YDU_fUK6?YOeWEr_?SIj%Zz|4m`bf?;7VQ%@96hRk$NcjJy6oW>}bE_wa< zwNqD5&HG?!4&dkfJLkW1abbY2nNQ{%gstG5Jc<-A+j`5B%-J2wAmLqPGV*%$wYsZy zuQkAw*~xg#rlb?m#oSR;AULxzc)BwN6QLBddxi6L`fs zRV&-NlsJ-0Rj$S=mdYig+NyP`Ui%uW8#h<#2A~?V`q5mc|KgsK*KP(|yISx-KK4p$ zm;XDg6<^ls1^bV*jEGv{|AAg~n}tBK{8w`Q6TSW$y_8jIVN{2Jng2f&^xx@qpI*wE zq{%0)NnuH5DoZj_`l!|T53~^f5XqT=goiK0m20o&?fM(_$+9+JCMVxH`JHDM4&CfN_eznu50Q_D@kL8 zUDT6-VT5Vyk~kzUuL;yagQF+)1kB@OoF;6@N0!z~wNqqPP-7yhl0A>kuTwW=c>Wdf zICbWPogJUw4oMS9yEJM#P$}iBd{tvprT9v&m$IS`1)q}I0iGR7=(S6+RO)kfm2|#F z3Rk{TfAfE@PNQ|8UX_oQbWGfT`0q#UTB!#bl`aesvlqXKP#QOH)ug zm(SZ5B$$mpq$XN<^vW~rv*t6x>FnC7M=>qHWd5#+99&iY=oz|YRTlWEn!mCc0worX zlqs-hhvF}}q!EzjCH8xlh4NM9tRbU-L{c1a>AOOH`nZyhG1z1E%z#0(sGg?t4<*Iz6S&+2Kw4JIYo{MBkgmysoOk|)%zYcCb z;I-llljJGnVw+K@U`R1-BmOb&uY)s#*>VUmy98-}Rb>C`*8mM4V;0>mtH=m1h>CfJH_N|_zcU#i49h4;Qwi=oonmsso zQ9f^#-5xXIg#W9!QTE#SRpJxX-Ka}?HY8nbFk21-2P4xXacgbN6bn=CN2ZT3igfDa z%=6RFCv&Rj{E3`;;J}V4s%D(bcM_keV)n~#RKMADy@`x56jse`AdVjRv%7BWS}feY zR9Hk@$ZJ_wvjhv)V2t@%-_E*`wQzdj%ZY~V56q^*{J(A3K6_xfq-t*4{E5Yqw))@Ed->nZ|6bX5%Tn79Ch*^LXsNz!YR^p9bl1$2(@!pXYM91rO~O&L=%|CW_iLF~ zGv_wEmNj`W)-mNFZX0-buN%o5to#Gl56nOFZr^*scY@zJm#omrsZvfA^`JLul;bdE9vUndZ3kK7C+w`{Cj=83}vIRrZy>TtqbKCW8^PS(`z36CL za=NCrT#hVN)=Zf`au>pK`n8=`ccRNLT)*(`@r9aX$=0dPrG~~S*r(STlYO_^lJ&b6 zU5_pKTH>x28kXOwxxYuR%UgGUuU=PD!Q=zQ)$D`j+OOKdURVhJ!2Ln#`=uWSl6y`h z3!jcvnc9)_^M z;v7J+UGP!A72-tm1iDPRXpt-0wO4K&R2YKt_F@M4Z-|=u^$`$li~v7@+TcV{FF}sT>`CI{J*G5F;GfC)7=(ppF$1aQF_JanXfBTA!pL#jE~cSN z6w(gq2Z1*R2-!FZAWZ;eSppp{N-VWhj8{ac0-Y8bKuvJTQ*O!ZzH0}r9!Qn8B=8@y z6w6Xg!<&b$ADSO~d*a4KvIbJ5%9wrXl&E?9dUdM!|10jwgQK|8d-u%r+?S-$eT`0Z za~hBU+X8fP2#kac#)JhSkc2>X&#=HCIk1(gh|N|k?~+=vj)Rn>3XydRv75vqyIY84 zYwe^qwbLH$jy#|gRa^ce`6q*iE!*Uu{Jz)I-6PTR5pOC}((8AA@Ad0{(__l>%uIjK;%Hr&-;?8w6%+A2b(VBlyq>F2L9Mc$($1z-aC_Chl%4ADI zh0D>DSTZd~hZ~U3-8?SAQ=GRO3OKZRuSu9^{Rgj2Ar1%+FFB`RuHO`k* z+{l~En`)uq8!6ccZ49IKu^rgr#;eEkCz>ZtOdObcVJb8=I8zcPv5e%nf8g{)orRLK z@XvebdUMJ){mi=?Z*QD=Ia1XAenrH&7ng1x`=}9Hqc>x0XV_C5wiZ9lfUgp8x8v}; z9Pvp1b1xczu$hVjzX|^W{y{F(TXn&_^EX&`Et)0{$bPA7tsa3$IrxePhAq06FPz>w zt9PM-^;%~r_p7=WfT~v_kIu!7eo2OO>$ZvQEkrQFxLW$tt~%JX1)KMoXF`$0Q@9-LQ$LTOi{hxoEXTI{c`O@uV zU(&7#S1j}8bm1+U1N1e`df!SDN6{k@qDKpsjuVh$tjgF|>LA2jFQ-_x%ox(meVh-W zvSAy!8f57C%hZR03y!Dr)|#-n45*|^t@R-2ng&X=u;PO_MshYNZv}w`T5j2IKnLYd zNiA3MX-}Mb1W17V%wV)7HPML>+rfE^Tm;>izC$`L!CcluwM)r{aeU00@RhA#Y`ul zK2V%M))a{@CV$9~bwZP4MqEwGxH2KxF)L&a&%vr>Kpdo?B^itvafcZ*%VLPkkd}O^ zW0$5&#KYWd&lKs#o%prRK?mcR9$>|A8DAE-w#1Xn2M#w%M=%hxlkXJx+D-<_adyATxUfXkk_NR_0lBXG1DtmcO&1)HSrdW}qsdyhWwg2d)iF z`EKRj%#9SSm8|)r*0Jh&UluepWMqz7=QFcM?a;^f{l=@reF3?(GH@H6zjl7kQ#I9f z>&2TdP6wkK_DCD{L^pIv8@dPyvcG8`--=Chqx%tOXmDfS%+8NyHAq>oOMfeLGbCj- z&8)q<{?2+SYb%ufC2_p9Z+R3PFEi>akemhM-E+=Tuw4;X%=ornJN;1Y<|~9!3Gnue z{MiWjiafdV-t6zY-gL=rdTca!-4D}q35$=WmrCiS6UU>K&q|fgMk}{Um0M@mz1MWF z>3uO$xlc;p2TNE{|3=BbamIHy?@pfN-#+`&dx!5Gj<)QV@aKPV%yQMGv~I(VEt6X! zIjfZ>W~oPpQ`b&G;IsB-?Q|egv}w+{Sz(d!=0?4Ks4bkxzL7tfKNXNl)=YOxC5^xF zHa%3+d{d^${NH%%RjDJeWy6>*p0lur5+tEG%;Kq0=bK%{0cve4F-#Roc|6yU+-L#LKf7y8Y3k=y7(eSD*b>d zLOb~|QzXOI2Sca03R4%VkZ28H(^%#&wRC|ILF_#qGxh|}Cp9HOPp2HIO}ChXW@0jW ziN2&*67xzzS|0OCX4pq_id6MN_Z6r+#42>|`pC7Bh_C*N4OZ;Bi8t=lo2MqqCeGcs zIC(LWwH60ZPadcc<=23#YbsBYZ_8epkQIhX{Q@tT%G9HOj!e1k(dl1hzX+sZg;sYA zRu6Nui|(%FL~nV$(}}X4vvU|uuYj(iL>lyzZ?AX3s1b~H&X#Os=#altVCubZvht_* zHl@Xk_Mms=ug|cf?Y#IJ^cxY5<4O|@K14krR8FUjCrm_90P<)`B5j7wkhdaWzgQ8# z9##=(fPVxR{X`@dSK;H!SN@EI{4*NX+Sq2h53v%5&hBKd+JkJv^00gT?FF>d5(@PzZq1beL6%%i{rS6Bw zL_gdxCl5_H1nMGbL3L1m-m)gXre9hQiNn;=;j_`3Zpf)K%@!ePgb3(B(=W-6_PTGW z*G@UNrkds}^pwu@R~9aDyM9mop58-e^6I@CyWw4iQ%Db0R~8#(dHf1}p@SZ#jHx_% z9HzP_RIWsks8~j#U%hPv?}*ANv4|_epITP-E3H+YxW5OVDN`GSFT)nhGM!S%DBRck zAk8QspMa9)6uC*WhBoWX1{g+G>2-Qin98hE#zInV(t_pp>(o@a{JSiv$qGKtY9;so zzCSU7{R$NAiP#z~r1+GLYs3aZkRAUH;&%nwWMjmLdmy1>1%hfO7x*f)waA?^Mm}#g zeJiDmA*GB;MpD`V^fq}Y5&OWW@g?U8lt9t(Wy$XpNGM_pX6)yTBK2kMt}6@sbq>A?mieb}YP(L!r3xIY|va8l;h z8U>2>)NZ|%Sq}mUtsTbUH0Gj}Zwn*oAe!j3@e5$9(&ZZLloIQ-Rw%xdRGr?%Q4snA zhCLeJcM8dZcs+=Uz2x@=t=_mKJAxoVn5aKdQ-(73wf2L*$2(#iA(IY2XY!7!i4G0Y zaZ}2B&_G-AgOQT2wBDuU)`ONp2>vpw=NCXrA#wg5pgB`&D__zL2rgaOf0#rCMtom( zuYw_@?O0W+Q|i>scv6&Bq{vTao)LyE$IWbBWeyXEO7qjLN9;D8Sf4UllqVsq zNq!QODgkRw^;KKs+Z=Y4BNBN=ETzLeV>zMqx4Km36JeMFUFXP{!eT0y=%<2=Bji)c zvKs|u6oRo1|0l&|lk*WJy}mJ#vB1s=ba1~AOf0CrA)Y=3W#~|vRm1h^9)p`9!cJ(60Hp(0FK$1kM37trTxNb znbAZct0@dFL~E0;t+LQ5i%DxrXwh|SQdnYnqChgkaYBx!v2%dXmde)QfPD(@8y#0V zt_Db%FUVF-`$JH0`mglAF?eNg{CKozy;QV5TGS*JHO;KM+jOUCR*V$wkleVf)v^I?{qwHWR4vtIXrL%6iyb-IabcM@A{SP zUu>h>qfTP1m|(^pBqzbhy`39f-7KwchD8hsf6kU=NP-$v#!d20fGBz4lBPMwrun@5 z@s9CL6Gy{2Rihmb)@=Bxac2A7_B-uAam}r1jpVe!@e230@U<%U(HC zIO+&zwLNfouV!8ijIX0x!tael(kfsz;{4=!GV(z$7IAGIHO%`0{kx{f2;=j ztjj7SXE9i6#Iem}SA?`(7v3zK^KKyfBH}Zn z!|Hm<|HI-jXVhCJdCTU!mGj&8j+vrfm@0vuwp)2O^Ca)O=>zW`x_wCUHqVz<-l&|z7sT5N zt`z_o&#j$v)N@Eb{NknuZ&kg=Py}fnRgcBW|c-cv%CUMgtvA?oGX;L z0dOT%O;-I?O~hHR#Pvcn2f~4= zou7TYhv#zJ`Ojh%`#+0iwFT(h{0nn-ceUvkWh=WIOz-8a>|Sev$Wt5%Pwe3F^A9sK z$jn;h6W6w(Gbj2_DseGdTmPP%X>xAEfokUO;_<{zS{i+;|Vd*k?Kg1CCNcz4g`2g7kARS${>&rTqwIxScW^HlWs~$r&3Q`1YafY zJPnoFB6a{0lKx>az+F_Ki3xN+fs8xAbal(E^fA**hbWHPT1RRt417lKHltx!TA@*_Tf@M+CY87abRLoLC*;?C^@oLK%32!%wErQde z!b$EfGMtporE!r2QXrCapjbc-;r`;c$=N{8f5C}41F%`u-*dV**asz_S16tx0~m>s z2@kOyoe2rD&75(l(vB`NqU3MM_lTSXIW}r+8aV~z)RVK8ob}|i!HKz&Mp%1#2M3QF zspi>PB7nl&^K5SwN2w9y^dfuU7CfK+5% z*pSFe7itn!CgItH^BgY}Cq$ziFmkMLd@F^WeBv0-N?8Se!dHY0&m+7d;j|06pX96+ z_VA3J$lib^v-+dsERFzt;!}9O&)HIiO!1~mf5TA8|Bgz=;kgTZ8K=?pH8fr0GB{x@ z=R_mV9po+;gDT3M5~mXR5VrvaeRw_IjG)S|p+`7ON!1}K^cleOAspmj|CE27zhL}U z(szzO$DKDGxh7^7R3TKE0;92+zlHiRu*vt z#)mtr?P5E+K4v|A^i(hE3dGV{VLTfeL0Vy&{Z z)BUgR%f3CWT`_Y@=bo;EU0Bb1DJcvm)wGKE3dNH113E9Bh6kEy z?OwtCz-oWPam6v#F=hI-wLb2L%=)K~i#b=(9OO)&3p=)b%$bDDM-YI`eBj9YnB#@a z4?VsIj`WW?i{OT~0jKMY4Occ?Jxi(({<`JHuE|~DtksfZ^@EI@d8_Nc;~r#t5~x6p z-^$M8e&B0vG)`OL{3AK{40X-xjQ7^^@u9Sr8N7y>f{*Ewzd+RT5i-+Otuv2CC|#RbtPMw zaCrGSAckxmdk!5VgN}#A0fe8phXM_>8xX#9b}%URv&i6uS9=2u+n~d`8$Nj>V&@pU z*oh1xk!Dy{@hC0v1WEh``TEGmUYQ+3*g?iDzM#g|kdM|7dHbr6x5342sPiwb{xI6k z&QuJpR>SiE6Uw9=m#mB*Iz;D+I(95OEJM#+3`aSi{Rri-0MLO<+(C`j4s3CXYo|EY z&<^sk7fBxCK0|uUJalXT%axpvJf1Di905v%Sz|&fb$Dzah68N3t7-8FDNVi?>)nz`bg+++w*~*m)3L z%Je<`jOrrAKk_M&ZU$4%q3o@b=TO#a`G6;OD(@0XW9`S%&5mq1^U;ZIA3;$Mz#%tj z^FD+gQI9yiRO^yaViM`XC?%HCt6!CmF2@NXmK&MqLG+2iL3{!%x&dYtUS4n+>kZQf zCC*UZt#D!)vO&$k{$mZNG4uN&l5!#D)*{aa`v=9H1phs9{)q~*y@9y4`%p}760Jev zEI9|s*+F^#7QUDngA6unM55=3v9q5T z9?IlZGkvpHq#Z@{QgoUUeN@rDC8kllTZxB&4bTrZtzu3klGHMZ8I;eXL@6)iL#&ud zNiHP`OF*DcBmzh*ONmpfYhZmuH|63zdT%!SiN+=pDUEgJVDD+D7GoN#Vm5h23?4lh zvnQ<~b~m@hEPD@bJJNEXTRco9&jS#ct&a7?+`C~xbuapwtl>gl$V|4{puwt=vb|5v zH^@0c&L5C7PtFr^J|V|OO>&V#LRm7^tcrX!plqO7G^vS49QOru+W6AzJ5qvnfI}$_)7v}WMy|_k&GF)_kS~HZ5_4akOupbFTZG{XXYG z&IK-ify-nrJoxi}#I0Z8su#Gm3tagESGvHJAaa4LTHq=X{GFBKck_?ToGER=wCWQx z&)XhxaG+=?qkgO^oL?)Y*G1ABE_twBrTL=XQpsBy_0~z=I*1ia9hg2jeK72L?vj1p z?wQE=0G42llD+N{)bqQ?@+3$8_({o8amk7bG7G=ABg~~eHos=%U*g9PUO#;8aI~;d zDr}r-yu0PjmguHE(xyF+IhGvnvv_#_yfrOuq^G|@ey-cfo(~0ELO^`HJd5|vJ96Vj zJRe%K;sTy%XSzRbA)l4AyW=+U**UX4;eao(#>)F1<#1be@^NDWpANc-{LIdmpXH70 z`B2D92uP3j@mz5k*dgLJ3%_~ZmJv7N`Oujb7x1LI3gQ;>Svgl8%ag1NaS2}<|6HQV z&X*+eGI&p-&cgc=J_BD9x2K`qwyd}j&xfx3xPT{MIdkI{^1-@qaok2ewA+?&z?Y~p z@T(JcBfmE8*sg^g6n0X$?Q+{_SKLKmw-)wL z*h}H|%k85*@iYpjYv~yj_EC7(ML3AC}G3plQ$`X@<;< zupy$B!yaI3t9JKfHr`aIBvo-YM-{$BsRuFj9(`%7XpnF=HHPdX@nZiirwKLg@ElF&So7|E4pGOl?SLuq%~ zonhx$n`YCV3^&i&v?uM&@bf&-+`Lcn&if^PKHx)A$cJiwQoZE=)H5HH8l=Fd?0g-a zlftzUFm9BBAQO_Bq&kqPU-w0uH}ZE3IV~G9(eaY1VMEI*3DJ<1Wi0AC(F|Etu$nbi zvqsGCAJ1wUQ4B1RB{7>e@FGcyYqFZvu&iT4Ew3guYstOUw5WhaRnf6%_zj|EWJOHl zw5$;QJkBa6c?3m1r$sR956rnHxtr6bF+wrstgFCD=&9iB(Uy z=9I9!1hWJ$%WFiz1_3W+u#=uJNz2RfVw%LFE~`ZV1G8yKC`F72R0`OYagENX+X~bS zE!LLxXT*C#nkdUgQm0m9j9+EPM;Gou-1UXHm|2)0ddg5&7vi!+@Q0eZLK23)Fa;++ z_Tda()A3YV)Kd#L#6*f{ctTxK7h+)b+QKb)aREX`23BE8TqgR0u)0ot8Lap`ST;1a zx^6X>2ZFpLXyi9pStF8G4@Nb?Uq1!S7JA7e{?zW=_ulU3^*wtZ9n@bm`HO|X36ni> z3<4-o2?z`-!TKBq_aOi)ROF3sr4p>_gu#~S6|PKG`*KJ(nH(cADM*`pjMl;+!&jDB zu~DEra{5Q5VUVVQYvb}Sk_1Am(d2~`b&}!#M*6iosVcD}*bqfIi%Zx^Y}G9jL#SL3 z%>%Pm^Uo)5U;iLJGxsZDEFPb{JvZ~gY+R#WS#_nWBB+XxkfSawNV#4|0nw>)jI|JS z8uiC+I)KnepxHu)p~f##JE^CkzI>?fTkdK9c)ox9AT+TxT?jOO`LmrX`+?zajHjI! z^PLwDI)}~3u*nW<4Y2mLd*lTM96WRUI|vwrhS}l_U^yQgtP94}6#LtB8PqHLlUyrw z?Hgb9o>hY5&bcJ_2FPu)5GK~9JXJl_6**6;9sKm>+&R{!T=b^q0FZM`JKE$V&!)G! z?nn0L%KMgk&@};C>#9kS<8rP?C7(7q#U*jIdaL$S_sUhYZa_k9@;OfOwt+`lQ<;)@ z>K7edrM*`RVzDC%LL2QsO=UrsQ##8=fx`X` zkL|C;=o@~VlqHEM(eoJYba-NNYV6LfIpO`WUkbNoW+&sXv&L_X-Huys0Bnn0QDwzC zS;hLrb%7q6V7q7e?!?EYCx!Rp(@~!lun$eJeYAWw(Q8k!6>O*9c| zjX+m8Vs9`VjV)#5mV}z)wn9}lU0elt$+pBXRHqz`ZYMsEmS;# z_^|11ecs%=?JIa2%H&H9wMKS_{xI_O$ljHM$l$@r3ntrKY)8JvFR$-}if-g-E%GSR zvnPH%V6rD)@PY034)|8HV{o4zECf4DJiH$qKCEvy`S!z(zK8yYZDt36R$HN^ZEN<0 z*S|gV#mLTx+0=g!h#q*)nFH_cd*6MIM+(8Nz01dLSC>D}pMovgJGVnGBBytMdl31_ zw+VA-`XF{AAGu+&tp#t#?(KaqKJ2;tq&wep?K@AtXVQc^wwUL;o(0+tgLrQ&AMDKs zagjs4=gLz>mnX;-e5Z?U=nr{+(E~k51bWTh%Vy8$liotO_hEQHd~RpxvD@@tdVKS5 z>rYZeH{*McDSD|e@_0B0eJf}RXG+o3M&Iv%fTt(}2upL;{;Ehcfj&Nq&;qR7bOVaI zYTh;MqmsOhKa7oa@Q76!eng5QlI!z+mf|Y<#0|LW$)TK^qS+>w<1~K`FyBTwFQ6G9 z8AaZlXQhJsCF7v0q9TKG9$HLiHR>EZaC}6`W*}z)I52Q5PF!6jiWKFw9vEui46>Tw zNkS#0+S?MUFl9v|_bh+)!dMN}G}ed?PhQKvL|?CV5?gImx_T8?;c6N>eN~q7x>QhU zK5MjDg1^244HVS&2&ConvxAQV|2TU!&v$Q!cKwCG=|4B^&Ht%0AGq-N@_yjzvtYQu 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 k^6j~?D5hOQG3^?PY1bfTy9V*2Fki-3JX)#c None: + self.config = config + try: + self.fnpats = config.getini("python_files") + except ValueError: + self.fnpats = ["test_*.py", "*_test.py"] + self.session: Session | None = None + self._rewritten_names: dict[str, Path] = {} + self._must_rewrite: set[str] = set() + # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, + # which might result in infinite recursion (#3506) + self._writing_pyc = False + self._basenames_to_check_rewrite = {"conftest"} + self._marked_for_rewrite_cache: dict[str, bool] = {} + self._session_paths_checked = False + + def set_session(self, session: Session | None) -> None: + self.session = session + self._session_paths_checked = False + + # Indirection so we can mock calls to find_spec originated from the hook during testing + _find_spec = importlib.machinery.PathFinder.find_spec + + def find_spec( + self, + name: str, + path: Sequence[str | bytes] | None = None, + target: types.ModuleType | None = None, + ) -> importlib.machinery.ModuleSpec | None: + if self._writing_pyc: + return None + state = self.config.stash[assertstate_key] + if self._early_rewrite_bailout(name, state): + return None + state.trace(f"find_module called for: {name}") + + # Type ignored because mypy is confused about the `self` binding here. + spec = self._find_spec(name, path) # type: ignore + + if spec is None and path is not None: + # With --import-mode=importlib, PathFinder cannot find spec without modifying `sys.path`, + # causing inability to assert rewriting (#12659). + # At this point, try using the file path to find the module spec. + for _path_str in path: + spec = importlib.util.spec_from_file_location(name, _path_str) + if spec is not None: + break + + if ( + # the import machinery could not find a file to import + spec is None + # this is a namespace package (without `__init__.py`) + # there's nothing to rewrite there + or spec.origin is None + # we can only rewrite source files + or not isinstance(spec.loader, importlib.machinery.SourceFileLoader) + # if the file doesn't exist, we can't rewrite it + or not os.path.exists(spec.origin) + ): + return None + else: + fn = spec.origin + + if not self._should_rewrite(name, fn, state): + return None + + return importlib.util.spec_from_file_location( + name, + fn, + loader=self, + submodule_search_locations=spec.submodule_search_locations, + ) + + def create_module( + self, spec: importlib.machinery.ModuleSpec + ) -> types.ModuleType | None: + return None # default behaviour is fine + + def exec_module(self, module: types.ModuleType) -> None: + assert module.__spec__ is not None + assert module.__spec__.origin is not None + fn = Path(module.__spec__.origin) + state = self.config.stash[assertstate_key] + + self._rewritten_names[module.__name__] = fn + + # The requested module looks like a test file, so rewrite it. This is + # the most magical part of the process: load the source, rewrite the + # asserts, and load the rewritten source. We also cache the rewritten + # module code in a special pyc. We must be aware of the possibility of + # concurrent pytest processes rewriting and loading pycs. To avoid + # tricky race conditions, we maintain the following invariant: The + # cached pyc is always a complete, valid pyc. Operations on it must be + # atomic. POSIX's atomic rename comes in handy. + write = not sys.dont_write_bytecode + cache_dir = get_cache_dir(fn) + if write: + ok = try_makedirs(cache_dir) + if not ok: + write = False + state.trace(f"read only directory: {cache_dir}") + + cache_name = fn.name[:-3] + PYC_TAIL + pyc = cache_dir / cache_name + # Notice that even if we're in a read-only directory, I'm going + # to check for a cached pyc. This may not be optimal... + co = _read_pyc(fn, pyc, state.trace) + if co is None: + state.trace(f"rewriting {fn!r}") + source_stat, co = _rewrite_test(fn, self.config) + if write: + self._writing_pyc = True + try: + _write_pyc(state, co, source_stat, pyc) + finally: + self._writing_pyc = False + else: + state.trace(f"found cached rewritten pyc for {fn}") + exec(co, module.__dict__) + + def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool: + """A fast way to get out of rewriting modules. + + Profiling has shown that the call to PathFinder.find_spec (inside of + the find_spec from this class) is a major slowdown, so, this method + tries to filter what we're sure won't be rewritten before getting to + it. + """ + if self.session is not None and not self._session_paths_checked: + self._session_paths_checked = True + for initial_path in self.session._initialpaths: + # Make something as c:/projects/my_project/path.py -> + # ['c:', 'projects', 'my_project', 'path.py'] + parts = str(initial_path).split(os.sep) + # add 'path' to basenames to be checked. + self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0]) + + # Note: conftest already by default in _basenames_to_check_rewrite. + parts = name.split(".") + if parts[-1] in self._basenames_to_check_rewrite: + return False + + # For matching the name it must be as if it was a filename. + path = PurePath(*parts).with_suffix(".py") + + for pat in self.fnpats: + # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based + # on the name alone because we need to match against the full path + if os.path.dirname(pat): + return False + if fnmatch_ex(pat, path): + return False + + if self._is_marked_for_rewrite(name, state): + return False + + state.trace(f"early skip of rewriting module: {name}") + return True + + def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool: + # always rewrite conftest files + if os.path.basename(fn) == "conftest.py": + state.trace(f"rewriting conftest file: {fn!r}") + return True + + if self.session is not None: + if self.session.isinitpath(absolutepath(fn)): + state.trace(f"matched test file (was specified on cmdline): {fn!r}") + return True + + # modules not passed explicitly on the command line are only + # rewritten if they match the naming convention for test files + fn_path = PurePath(fn) + for pat in self.fnpats: + if fnmatch_ex(pat, fn_path): + state.trace(f"matched test file {fn!r}") + return True + + return self._is_marked_for_rewrite(name, state) + + def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool: + try: + return self._marked_for_rewrite_cache[name] + except KeyError: + for marked in self._must_rewrite: + if name == marked or name.startswith(marked + "."): + state.trace(f"matched marked file {name!r} (from {marked!r})") + self._marked_for_rewrite_cache[name] = True + return True + + self._marked_for_rewrite_cache[name] = False + return False + + def mark_rewrite(self, *names: str) -> None: + """Mark import names as needing to be rewritten. + + The named module or package as well as any nested modules will + be rewritten on import. + """ + already_imported = ( + set(names).intersection(sys.modules).difference(self._rewritten_names) + ) + for name in already_imported: + mod = sys.modules[name] + if not AssertionRewriter.is_rewrite_disabled( + mod.__doc__ or "" + ) and not isinstance(mod.__loader__, type(self)): + self._warn_already_imported(name) + self._must_rewrite.update(names) + self._marked_for_rewrite_cache.clear() + + def _warn_already_imported(self, name: str) -> None: + from _pytest.warning_types import PytestAssertRewriteWarning + + self.config.issue_config_time_warning( + PytestAssertRewriteWarning( + f"Module already imported so cannot be rewritten: {name}" + ), + stacklevel=5, + ) + + def get_data(self, pathname: str | bytes) -> bytes: + """Optional PEP302 get_data API.""" + with open(pathname, "rb") as f: + return f.read() + + if sys.version_info >= (3, 10): + if sys.version_info >= (3, 12): + from importlib.resources.abc import TraversableResources + else: + from importlib.abc import TraversableResources + + def get_resource_reader(self, name: str) -> TraversableResources: + if sys.version_info < (3, 11): + from importlib.readers import FileReader + else: + from importlib.resources.readers import FileReader + + return FileReader(types.SimpleNamespace(path=self._rewritten_names[name])) + + +def _write_pyc_fp( + fp: IO[bytes], source_stat: os.stat_result, co: types.CodeType +) -> None: + # Technically, we don't have to have the same pyc format as + # (C)Python, since these "pycs" should never be seen by builtin + # import. However, there's little reason to deviate. + fp.write(importlib.util.MAGIC_NUMBER) + # https://www.python.org/dev/peps/pep-0552/ + flags = b"\x00\x00\x00\x00" + fp.write(flags) + # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903) + mtime = int(source_stat.st_mtime) & 0xFFFFFFFF + size = source_stat.st_size & 0xFFFFFFFF + # " bool: + proc_pyc = f"{pyc}.{os.getpid()}" + try: + with open(proc_pyc, "wb") as fp: + _write_pyc_fp(fp, source_stat, co) + except OSError as e: + state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") + return False + + try: + os.replace(proc_pyc, pyc) + except OSError as e: + state.trace(f"error writing pyc file at {pyc}: {e}") + # we ignore any failure to write the cache file + # there are many reasons, permission-denied, pycache dir being a + # file etc. + return False + return True + + +def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]: + """Read and rewrite *fn* and return the code object.""" + stat = os.stat(fn) + source = fn.read_bytes() + strfn = str(fn) + tree = ast.parse(source, filename=strfn) + rewrite_asserts(tree, source, strfn, config) + co = compile(tree, strfn, "exec", dont_inherit=True) + return stat, co + + +def _read_pyc( + source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None +) -> types.CodeType | None: + """Possibly read a pytest pyc containing rewritten code. + + Return rewritten code if successful or None if not. + """ + try: + fp = open(pyc, "rb") + except OSError: + return None + with fp: + try: + stat_result = os.stat(source) + mtime = int(stat_result.st_mtime) + size = stat_result.st_size + data = fp.read(16) + except OSError as e: + trace(f"_read_pyc({source}): OSError {e}") + return None + # Check for invalid or out of date pyc file. + if len(data) != (16): + trace(f"_read_pyc({source}): invalid pyc (too short)") + return None + if data[:4] != importlib.util.MAGIC_NUMBER: + trace(f"_read_pyc({source}): invalid pyc (bad magic number)") + return None + if data[4:8] != b"\x00\x00\x00\x00": + trace(f"_read_pyc({source}): invalid pyc (unsupported flags)") + return None + mtime_data = data[8:12] + if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF: + trace(f"_read_pyc({source}): out of date") + return None + size_data = data[12:16] + if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF: + trace(f"_read_pyc({source}): invalid pyc (incorrect size)") + return None + try: + co = marshal.load(fp) + except Exception as e: + trace(f"_read_pyc({source}): marshal.load error {e}") + return None + if not isinstance(co, types.CodeType): + trace(f"_read_pyc({source}): not a code object") + return None + return co + + +def rewrite_asserts( + mod: ast.Module, + source: bytes, + module_path: str | None = None, + config: Config | None = None, +) -> None: + """Rewrite the assert statements in mod.""" + AssertionRewriter(module_path, config, source).run(mod) + + +def _saferepr(obj: object) -> str: + r"""Get a safe repr of an object for assertion error messages. + + The assertion formatting (util.format_explanation()) requires + newlines to be escaped since they are a special character for it. + Normally assertion.util.format_explanation() does this but for a + custom repr it is possible to contain one of the special escape + sequences, especially '\n{' and '\n}' are likely to be present in + JSON reprs. + """ + if isinstance(obj, types.MethodType): + # for bound methods, skip redundant information + return obj.__name__ + + maxsize = _get_maxsize_for_saferepr(util._config) + if not maxsize: + return saferepr_unlimited(obj).replace("\n", "\\n") + return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") + + +def _get_maxsize_for_saferepr(config: Config | None) -> int | None: + """Get `maxsize` configuration for saferepr based on the given config object.""" + if config is None: + verbosity = 0 + else: + verbosity = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + if verbosity >= 2: + return None + if verbosity >= 1: + return DEFAULT_REPR_MAX_SIZE * 10 + return DEFAULT_REPR_MAX_SIZE + + +def _format_assertmsg(obj: object) -> str: + r"""Format the custom assertion message given. + + For strings this simply replaces newlines with '\n~' so that + util.format_explanation() will preserve them instead of escaping + newlines. For other objects saferepr() is used first. + """ + # reprlib appears to have a bug which means that if a string + # contains a newline it gets escaped, however if an object has a + # .__repr__() which contains newlines it does not get escaped. + # However in either case we want to preserve the newline. + replaces = [("\n", "\n~"), ("%", "%%")] + if not isinstance(obj, str): + obj = saferepr(obj, _get_maxsize_for_saferepr(util._config)) + replaces.append(("\\n", "\n~")) + + for r1, r2 in replaces: + obj = obj.replace(r1, r2) + + return obj + + +def _should_repr_global_name(obj: object) -> bool: + if callable(obj): + return False + + try: + return not hasattr(obj, "__name__") + except Exception: + return True + + +def _format_boolop(explanations: Iterable[str], is_or: bool) -> str: + explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")" + return explanation.replace("%", "%%") + + +def _call_reprcompare( + ops: Sequence[str], + results: Sequence[bool], + expls: Sequence[str], + each_obj: Sequence[object], +) -> str: + for i, res, expl in zip(range(len(ops)), results, expls): + try: + done = not res + except Exception: + done = True + if done: + break + if util._reprcompare is not None: + custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1]) + if custom is not None: + return custom + return expl + + +def _call_assertion_pass(lineno: int, orig: str, expl: str) -> None: + if util._assertion_pass is not None: + util._assertion_pass(lineno, orig, expl) + + +def _check_if_assertion_pass_impl() -> bool: + """Check if any plugins implement the pytest_assertion_pass hook + in order not to generate explanation unnecessarily (might be expensive).""" + return True if util._assertion_pass else False + + +UNARY_MAP = {ast.Not: "not %s", ast.Invert: "~%s", ast.USub: "-%s", ast.UAdd: "+%s"} + +BINOP_MAP = { + ast.BitOr: "|", + ast.BitXor: "^", + ast.BitAnd: "&", + ast.LShift: "<<", + ast.RShift: ">>", + ast.Add: "+", + ast.Sub: "-", + ast.Mult: "*", + ast.Div: "/", + ast.FloorDiv: "//", + ast.Mod: "%%", # escaped for string formatting + ast.Eq: "==", + ast.NotEq: "!=", + ast.Lt: "<", + ast.LtE: "<=", + ast.Gt: ">", + ast.GtE: ">=", + ast.Pow: "**", + ast.Is: "is", + ast.IsNot: "is not", + ast.In: "in", + ast.NotIn: "not in", + ast.MatMult: "@", +} + + +def traverse_node(node: ast.AST) -> Iterator[ast.AST]: + """Recursively yield node and all its children in depth-first order.""" + yield node + for child in ast.iter_child_nodes(node): + yield from traverse_node(child) + + +@functools.lru_cache(maxsize=1) +def _get_assertion_exprs(src: bytes) -> dict[int, str]: + """Return a mapping from {lineno: "assertion test expression"}.""" + ret: dict[int, str] = {} + + depth = 0 + lines: list[str] = [] + assert_lineno: int | None = None + seen_lines: set[int] = set() + + def _write_and_reset() -> None: + nonlocal depth, lines, assert_lineno, seen_lines + assert assert_lineno is not None + ret[assert_lineno] = "".join(lines).rstrip().rstrip("\\") + depth = 0 + lines = [] + assert_lineno = None + seen_lines = set() + + tokens = tokenize.tokenize(io.BytesIO(src).readline) + for tp, source, (lineno, offset), _, line in tokens: + if tp == tokenize.NAME and source == "assert": + assert_lineno = lineno + elif assert_lineno is not None: + # keep track of depth for the assert-message `,` lookup + if tp == tokenize.OP and source in "([{": + depth += 1 + elif tp == tokenize.OP and source in ")]}": + depth -= 1 + + if not lines: + lines.append(line[offset:]) + seen_lines.add(lineno) + # a non-nested comma separates the expression from the message + elif depth == 0 and tp == tokenize.OP and source == ",": + # one line assert with message + if lineno in seen_lines and len(lines) == 1: + offset_in_trimmed = offset + len(lines[-1]) - len(line) + lines[-1] = lines[-1][:offset_in_trimmed] + # multi-line assert with message + elif lineno in seen_lines: + lines[-1] = lines[-1][:offset] + # multi line assert with escaped newline before message + else: + lines.append(line[:offset]) + _write_and_reset() + elif tp in {tokenize.NEWLINE, tokenize.ENDMARKER}: + _write_and_reset() + elif lines and lineno not in seen_lines: + lines.append(line) + seen_lines.add(lineno) + + return ret + + +class AssertionRewriter(ast.NodeVisitor): + """Assertion rewriting implementation. + + The main entrypoint is to call .run() with an ast.Module instance, + this will then find all the assert statements and rewrite them to + provide intermediate values and a detailed assertion error. See + http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html + for an overview of how this works. + + The entry point here is .run() which will iterate over all the + statements in an ast.Module and for each ast.Assert statement it + finds call .visit() with it. Then .visit_Assert() takes over and + is responsible for creating new ast statements to replace the + original assert statement: it rewrites the test of an assertion + to provide intermediate values and replace it with an if statement + which raises an assertion error with a detailed explanation in + case the expression is false and calls pytest_assertion_pass hook + if expression is true. + + For this .visit_Assert() uses the visitor pattern to visit all the + AST nodes of the ast.Assert.test field, each visit call returning + an AST node and the corresponding explanation string. During this + state is kept in several instance attributes: + + :statements: All the AST statements which will replace the assert + statement. + + :variables: This is populated by .variable() with each variable + used by the statements so that they can all be set to None at + the end of the statements. + + :variable_counter: Counter to create new unique variables needed + by statements. Variables are created using .variable() and + have the form of "@py_assert0". + + :expl_stmts: The AST statements which will be executed to get + data from the assertion. This is the code which will construct + the detailed assertion message that is used in the AssertionError + or for the pytest_assertion_pass hook. + + :explanation_specifiers: A dict filled by .explanation_param() + with %-formatting placeholders and their corresponding + expressions to use in the building of an assertion message. + This is used by .pop_format_context() to build a message. + + :stack: A stack of the explanation_specifiers dicts maintained by + .push_format_context() and .pop_format_context() which allows + to build another %-formatted string while already building one. + + :scope: A tuple containing the current scope used for variables_overwrite. + + :variables_overwrite: A dict filled with references to variables + that change value within an assert. This happens when a variable is + reassigned with the walrus operator + + This state, except the variables_overwrite, is reset on every new assert + statement visited and used by the other visitors. + """ + + def __init__( + self, module_path: str | None, config: Config | None, source: bytes + ) -> None: + super().__init__() + self.module_path = module_path + self.config = config + if config is not None: + self.enable_assertion_pass_hook = config.getini( + "enable_assertion_pass_hook" + ) + else: + self.enable_assertion_pass_hook = False + self.source = source + self.scope: tuple[ast.AST, ...] = () + self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = ( + defaultdict(dict) + ) + + def run(self, mod: ast.Module) -> None: + """Find all assert statements in *mod* and rewrite them.""" + if not mod.body: + # Nothing to do. + return + + # We'll insert some special imports at the top of the module, but after any + # docstrings and __future__ imports, so first figure out where that is. + doc = getattr(mod, "docstring", None) + expect_docstring = doc is None + if doc is not None and self.is_rewrite_disabled(doc): + return + pos = 0 + item = None + for item in mod.body: + if ( + expect_docstring + and isinstance(item, ast.Expr) + and isinstance(item.value, ast.Constant) + and isinstance(item.value.value, str) + ): + doc = item.value.value + if self.is_rewrite_disabled(doc): + return + expect_docstring = False + elif ( + isinstance(item, ast.ImportFrom) + and item.level == 0 + and item.module == "__future__" + ): + pass + else: + break + pos += 1 + # Special case: for a decorated function, set the lineno to that of the + # first decorator, not the `def`. Issue #4984. + if isinstance(item, ast.FunctionDef) and item.decorator_list: + lineno = item.decorator_list[0].lineno + else: + lineno = item.lineno + # Now actually insert the special imports. + if sys.version_info >= (3, 10): + aliases = [ + ast.alias("builtins", "@py_builtins", lineno=lineno, col_offset=0), + ast.alias( + "_pytest.assertion.rewrite", + "@pytest_ar", + lineno=lineno, + col_offset=0, + ), + ] + else: + aliases = [ + ast.alias("builtins", "@py_builtins"), + ast.alias("_pytest.assertion.rewrite", "@pytest_ar"), + ] + imports = [ + ast.Import([alias], lineno=lineno, col_offset=0) for alias in aliases + ] + mod.body[pos:pos] = imports + + # Collect asserts. + self.scope = (mod,) + nodes: list[ast.AST | Sentinel] = [mod] + while nodes: + node = nodes.pop() + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + self.scope = tuple((*self.scope, node)) + nodes.append(_SCOPE_END_MARKER) + if node == _SCOPE_END_MARKER: + self.scope = self.scope[:-1] + continue + assert isinstance(node, ast.AST) + for name, field in ast.iter_fields(node): + if isinstance(field, list): + new: list[ast.AST] = [] + for i, child in enumerate(field): + if isinstance(child, ast.Assert): + # Transform assert. + new.extend(self.visit(child)) + else: + new.append(child) + if isinstance(child, ast.AST): + nodes.append(child) + setattr(node, name, new) + elif ( + isinstance(field, ast.AST) + # Don't recurse into expressions as they can't contain + # asserts. + and not isinstance(field, ast.expr) + ): + nodes.append(field) + + @staticmethod + def is_rewrite_disabled(docstring: str) -> bool: + return "PYTEST_DONT_REWRITE" in docstring + + def variable(self) -> str: + """Get a new variable.""" + # Use a character invalid in python identifiers to avoid clashing. + name = "@py_assert" + str(next(self.variable_counter)) + self.variables.append(name) + return name + + def assign(self, expr: ast.expr) -> ast.Name: + """Give *expr* a name.""" + name = self.variable() + self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr)) + return ast.copy_location(ast.Name(name, ast.Load()), expr) + + def display(self, expr: ast.expr) -> ast.expr: + """Call saferepr on the expression.""" + return self.helper("_saferepr", expr) + + def helper(self, name: str, *args: ast.expr) -> ast.expr: + """Call a helper in this module.""" + py_name = ast.Name("@pytest_ar", ast.Load()) + attr = ast.Attribute(py_name, name, ast.Load()) + return ast.Call(attr, list(args), []) + + def builtin(self, name: str) -> ast.Attribute: + """Return the builtin called *name*.""" + builtin_name = ast.Name("@py_builtins", ast.Load()) + return ast.Attribute(builtin_name, name, ast.Load()) + + def explanation_param(self, expr: ast.expr) -> str: + """Return a new named %-formatting placeholder for expr. + + This creates a %-formatting placeholder for expr in the + current formatting context, e.g. ``%(py0)s``. The placeholder + and expr are placed in the current format context so that it + can be used on the next call to .pop_format_context(). + """ + specifier = "py" + str(next(self.variable_counter)) + self.explanation_specifiers[specifier] = expr + return "%(" + specifier + ")s" + + def push_format_context(self) -> None: + """Create a new formatting context. + + The format context is used for when an explanation wants to + have a variable value formatted in the assertion message. In + this case the value required can be added using + .explanation_param(). Finally .pop_format_context() is used + to format a string of %-formatted values as added by + .explanation_param(). + """ + self.explanation_specifiers: dict[str, ast.expr] = {} + self.stack.append(self.explanation_specifiers) + + def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: + """Format the %-formatted string with current format context. + + The expl_expr should be an str ast.expr instance constructed from + the %-placeholders created by .explanation_param(). This will + add the required code to format said string to .expl_stmts and + return the ast.Name instance of the formatted string. + """ + current = self.stack.pop() + if self.stack: + self.explanation_specifiers = self.stack[-1] + keys: list[ast.expr | None] = [ast.Constant(key) for key in current.keys()] + format_dict = ast.Dict(keys, list(current.values())) + form = ast.BinOp(expl_expr, ast.Mod(), format_dict) + name = "@py_format" + str(next(self.variable_counter)) + if self.enable_assertion_pass_hook: + self.format_variables.append(name) + self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) + return ast.Name(name, ast.Load()) + + def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]: + """Handle expressions we don't have custom code for.""" + assert isinstance(node, ast.expr) + res = self.assign(node) + return res, self.explanation_param(self.display(res)) + + def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]: + """Return the AST statements to replace the ast.Assert instance. + + This rewrites the test of an assertion to provide + intermediate values and replace it with an if statement which + raises an assertion error with a detailed explanation in case + the expression is false. + """ + if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: + import warnings + + from _pytest.warning_types import PytestAssertRewriteWarning + + # TODO: This assert should not be needed. + assert self.module_path is not None + warnings.warn_explicit( + PytestAssertRewriteWarning( + "assertion is always true, perhaps remove parentheses?" + ), + category=None, + filename=self.module_path, + lineno=assert_.lineno, + ) + + self.statements: list[ast.stmt] = [] + self.variables: list[str] = [] + self.variable_counter = itertools.count() + + if self.enable_assertion_pass_hook: + self.format_variables: list[str] = [] + + self.stack: list[dict[str, ast.expr]] = [] + self.expl_stmts: list[ast.stmt] = [] + self.push_format_context() + # Rewrite assert into a bunch of statements. + top_condition, explanation = self.visit(assert_.test) + + negation = ast.UnaryOp(ast.Not(), top_condition) + + if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook + msg = self.pop_format_context(ast.Constant(explanation)) + + # Failed + if assert_.msg: + assertmsg = self.helper("_format_assertmsg", assert_.msg) + gluestr = "\n>assert " + else: + assertmsg = ast.Constant("") + gluestr = "assert " + err_explanation = ast.BinOp(ast.Constant(gluestr), ast.Add(), msg) + err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) + err_name = ast.Name("AssertionError", ast.Load()) + fmt = self.helper("_format_explanation", err_msg) + exc = ast.Call(err_name, [fmt], []) + raise_ = ast.Raise(exc, None) + statements_fail = [] + statements_fail.extend(self.expl_stmts) + statements_fail.append(raise_) + + # Passed + fmt_pass = self.helper("_format_explanation", msg) + orig = _get_assertion_exprs(self.source)[assert_.lineno] + hook_call_pass = ast.Expr( + self.helper( + "_call_assertion_pass", + ast.Constant(assert_.lineno), + ast.Constant(orig), + fmt_pass, + ) + ) + # If any hooks implement assert_pass hook + hook_impl_test = ast.If( + self.helper("_check_if_assertion_pass_impl"), + [*self.expl_stmts, hook_call_pass], + [], + ) + statements_pass: list[ast.stmt] = [hook_impl_test] + + # Test for assertion condition + main_test = ast.If(negation, statements_fail, statements_pass) + self.statements.append(main_test) + if self.format_variables: + variables: list[ast.expr] = [ + ast.Name(name, ast.Store()) for name in self.format_variables + ] + clear_format = ast.Assign(variables, ast.Constant(None)) + self.statements.append(clear_format) + + else: # Original assertion rewriting + # Create failure message. + body = self.expl_stmts + self.statements.append(ast.If(negation, body, [])) + if assert_.msg: + assertmsg = self.helper("_format_assertmsg", assert_.msg) + explanation = "\n>assert " + explanation + else: + assertmsg = ast.Constant("") + explanation = "assert " + explanation + template = ast.BinOp(assertmsg, ast.Add(), ast.Constant(explanation)) + msg = self.pop_format_context(template) + fmt = self.helper("_format_explanation", msg) + err_name = ast.Name("AssertionError", ast.Load()) + exc = ast.Call(err_name, [fmt], []) + raise_ = ast.Raise(exc, None) + + body.append(raise_) + + # Clear temporary variables by setting them to None. + if self.variables: + variables = [ast.Name(name, ast.Store()) for name in self.variables] + clear = ast.Assign(variables, ast.Constant(None)) + self.statements.append(clear) + # Fix locations (line numbers/column offsets). + for stmt in self.statements: + for node in traverse_node(stmt): + if getattr(node, "lineno", None) is None: + # apply the assertion location to all generated ast nodes without source location + # and preserve the location of existing nodes or generated nodes with an correct location. + ast.copy_location(node, assert_) + return self.statements + + def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]: + # This method handles the 'walrus operator' repr of the target + # name if it's a local variable or _should_repr_global_name() + # thinks it's acceptable. + locs = ast.Call(self.builtin("locals"), [], []) + target_id = name.target.id + inlocs = ast.Compare(ast.Constant(target_id), [ast.In()], [locs]) + dorepr = self.helper("_should_repr_global_name", name) + test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) + expr = ast.IfExp(test, self.display(name), ast.Constant(target_id)) + return name, self.explanation_param(expr) + + def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]: + # Display the repr of the name if it's a local variable or + # _should_repr_global_name() thinks it's acceptable. + locs = ast.Call(self.builtin("locals"), [], []) + inlocs = ast.Compare(ast.Constant(name.id), [ast.In()], [locs]) + dorepr = self.helper("_should_repr_global_name", name) + test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) + expr = ast.IfExp(test, self.display(name), ast.Constant(name.id)) + return name, self.explanation_param(expr) + + def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]: + res_var = self.variable() + expl_list = self.assign(ast.List([], ast.Load())) + app = ast.Attribute(expl_list, "append", ast.Load()) + is_or = int(isinstance(boolop.op, ast.Or)) + body = save = self.statements + fail_save = self.expl_stmts + levels = len(boolop.values) - 1 + self.push_format_context() + # Process each operand, short-circuiting if needed. + for i, v in enumerate(boolop.values): + if i: + fail_inner: list[ast.stmt] = [] + # cond is set in a prior loop iteration below + self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821 + self.expl_stmts = fail_inner + # Check if the left operand is a ast.NamedExpr and the value has already been visited + if ( + isinstance(v, ast.Compare) + and isinstance(v.left, ast.NamedExpr) + and v.left.target.id + in [ + ast_expr.id + for ast_expr in boolop.values[:i] + if hasattr(ast_expr, "id") + ] + ): + pytest_temp = self.variable() + self.variables_overwrite[self.scope][v.left.target.id] = v.left # type:ignore[assignment] + v.left.target.id = pytest_temp + self.push_format_context() + res, expl = self.visit(v) + body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) + expl_format = self.pop_format_context(ast.Constant(expl)) + call = ast.Call(app, [expl_format], []) + self.expl_stmts.append(ast.Expr(call)) + if i < levels: + cond: ast.expr = res + if is_or: + cond = ast.UnaryOp(ast.Not(), cond) + inner: list[ast.stmt] = [] + self.statements.append(ast.If(cond, inner, [])) + self.statements = body = inner + self.statements = save + self.expl_stmts = fail_save + expl_template = self.helper("_format_boolop", expl_list, ast.Constant(is_or)) + expl = self.pop_format_context(expl_template) + return ast.Name(res_var, ast.Load()), self.explanation_param(expl) + + def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]: + pattern = UNARY_MAP[unary.op.__class__] + operand_res, operand_expl = self.visit(unary.operand) + res = self.assign(ast.copy_location(ast.UnaryOp(unary.op, operand_res), unary)) + return res, pattern % (operand_expl,) + + def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]: + symbol = BINOP_MAP[binop.op.__class__] + left_expr, left_expl = self.visit(binop.left) + right_expr, right_expl = self.visit(binop.right) + explanation = f"({left_expl} {symbol} {right_expl})" + res = self.assign( + ast.copy_location(ast.BinOp(left_expr, binop.op, right_expr), binop) + ) + return res, explanation + + def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]: + new_func, func_expl = self.visit(call.func) + arg_expls = [] + new_args = [] + new_kwargs = [] + for arg in call.args: + if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get( + self.scope, {} + ): + arg = self.variables_overwrite[self.scope][arg.id] # type:ignore[assignment] + res, expl = self.visit(arg) + arg_expls.append(expl) + new_args.append(res) + for keyword in call.keywords: + if isinstance( + keyword.value, ast.Name + ) and keyword.value.id in self.variables_overwrite.get(self.scope, {}): + keyword.value = self.variables_overwrite[self.scope][keyword.value.id] # type:ignore[assignment] + res, expl = self.visit(keyword.value) + new_kwargs.append(ast.keyword(keyword.arg, res)) + if keyword.arg: + arg_expls.append(keyword.arg + "=" + expl) + else: # **args have `arg` keywords with an .arg of None + arg_expls.append("**" + expl) + + expl = "{}({})".format(func_expl, ", ".join(arg_expls)) + new_call = ast.copy_location(ast.Call(new_func, new_args, new_kwargs), call) + res = self.assign(new_call) + res_expl = self.explanation_param(self.display(res)) + outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}" + return res, outer_expl + + def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]: + # A Starred node can appear in a function call. + res, expl = self.visit(starred.value) + new_starred = ast.Starred(res, starred.ctx) + return new_starred, "*" + expl + + def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]: + if not isinstance(attr.ctx, ast.Load): + return self.generic_visit(attr) + value, value_expl = self.visit(attr.value) + res = self.assign( + ast.copy_location(ast.Attribute(value, attr.attr, ast.Load()), attr) + ) + res_expl = self.explanation_param(self.display(res)) + pat = "%s\n{%s = %s.%s\n}" + expl = pat % (res_expl, res_expl, value_expl, attr.attr) + return res, expl + + def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]: + self.push_format_context() + # We first check if we have overwritten a variable in the previous assert + if isinstance( + comp.left, ast.Name + ) and comp.left.id in self.variables_overwrite.get(self.scope, {}): + comp.left = self.variables_overwrite[self.scope][comp.left.id] # type:ignore[assignment] + if isinstance(comp.left, ast.NamedExpr): + self.variables_overwrite[self.scope][comp.left.target.id] = comp.left # type:ignore[assignment] + left_res, left_expl = self.visit(comp.left) + if isinstance(comp.left, (ast.Compare, ast.BoolOp)): + left_expl = f"({left_expl})" + res_variables = [self.variable() for i in range(len(comp.ops))] + load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] + store_names = [ast.Name(v, ast.Store()) for v in res_variables] + it = zip(range(len(comp.ops)), comp.ops, comp.comparators) + expls: list[ast.expr] = [] + syms: list[ast.expr] = [] + results = [left_res] + for i, op, next_operand in it: + if ( + isinstance(next_operand, ast.NamedExpr) + and isinstance(left_res, ast.Name) + and next_operand.target.id == left_res.id + ): + next_operand.target.id = self.variable() + self.variables_overwrite[self.scope][left_res.id] = next_operand # type:ignore[assignment] + next_res, next_expl = self.visit(next_operand) + if isinstance(next_operand, (ast.Compare, ast.BoolOp)): + next_expl = f"({next_expl})" + results.append(next_res) + sym = BINOP_MAP[op.__class__] + syms.append(ast.Constant(sym)) + expl = f"{left_expl} {sym} {next_expl}" + expls.append(ast.Constant(expl)) + res_expr = ast.copy_location(ast.Compare(left_res, [op], [next_res]), comp) + self.statements.append(ast.Assign([store_names[i]], res_expr)) + left_res, left_expl = next_res, next_expl + # Use pytest.assertion.util._reprcompare if that's available. + expl_call = self.helper( + "_call_reprcompare", + ast.Tuple(syms, ast.Load()), + ast.Tuple(load_names, ast.Load()), + ast.Tuple(expls, ast.Load()), + ast.Tuple(results, ast.Load()), + ) + if len(comp.ops) > 1: + res: ast.expr = ast.BoolOp(ast.And(), load_names) + else: + res = load_names[0] + + return res, self.explanation_param(self.pop_format_context(expl_call)) + + +def try_makedirs(cache_dir: Path) -> bool: + """Attempt to create the given directory and sub-directories exist. + + Returns True if successful or if it already exists. + """ + try: + os.makedirs(cache_dir, exist_ok=True) + except (FileNotFoundError, NotADirectoryError, FileExistsError): + # One of the path components was not a directory: + # - we're in a zip file + # - it is a file + return False + except PermissionError: + return False + except OSError as e: + # as of now, EROFS doesn't have an equivalent OSError-subclass + # + # squashfuse_ll returns ENOSYS "OSError: [Errno 38] Function not + # implemented" for a read-only error + if e.errno in {errno.EROFS, errno.ENOSYS}: + return False + raise + return True + + +def get_cache_dir(file_path: Path) -> Path: + """Return the cache directory to write .pyc files for the given .py file path.""" + if sys.pycache_prefix: + # given: + # prefix = '/tmp/pycs' + # path = '/home/user/proj/test_app.py' + # we want: + # '/tmp/pycs/home/user/proj' + return Path(sys.pycache_prefix) / Path(*file_path.parts[1:-1]) + else: + # classic pycache directory + return file_path.parent / "__pycache__" diff --git a/.venv/Lib/site-packages/_pytest/assertion/truncate.py b/.venv/Lib/site-packages/_pytest/assertion/truncate.py new file mode 100644 index 0000000..b67f02c --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/assertion/truncate.py @@ -0,0 +1,117 @@ +"""Utilities for truncating assertion output. + +Current default behaviour is to truncate assertion explanations at +terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. +""" + +from __future__ import annotations + +from _pytest.assertion import util +from _pytest.config import Config +from _pytest.nodes import Item + + +DEFAULT_MAX_LINES = 8 +DEFAULT_MAX_CHARS = 8 * 80 +USAGE_MSG = "use '-vv' to show" + + +def truncate_if_required( + explanation: list[str], item: Item, max_length: int | None = None +) -> list[str]: + """Truncate this assertion explanation if the given test item is eligible.""" + if _should_truncate_item(item): + return _truncate_explanation(explanation) + return explanation + + +def _should_truncate_item(item: Item) -> bool: + """Whether or not this test item is eligible for truncation.""" + verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + return verbose < 2 and not util.running_on_ci() + + +def _truncate_explanation( + input_lines: list[str], + max_lines: int | None = None, + max_chars: int | None = None, +) -> list[str]: + """Truncate given list of strings that makes up the assertion explanation. + + Truncates to either 8 lines, or 640 characters - whichever the input reaches + first, taking the truncation explanation into account. The remaining lines + will be replaced by a usage message. + """ + if max_lines is None: + max_lines = DEFAULT_MAX_LINES + if max_chars is None: + max_chars = DEFAULT_MAX_CHARS + + # Check if truncation required + input_char_count = len("".join(input_lines)) + # The length of the truncation explanation depends on the number of lines + # removed but is at least 68 characters: + # The real value is + # 64 (for the base message: + # '...\n...Full output truncated (1 line hidden), use '-vv' to show")' + # ) + # + 1 (for plural) + # + int(math.log10(len(input_lines) - max_lines)) (number of hidden line, at least 1) + # + 3 for the '...' added to the truncated line + # But if there's more than 100 lines it's very likely that we're going to + # truncate, so we don't need the exact value using log10. + tolerable_max_chars = ( + max_chars + 70 # 64 + 1 (for plural) + 2 (for '99') + 3 for '...' + ) + # The truncation explanation add two lines to the output + tolerable_max_lines = max_lines + 2 + if ( + len(input_lines) <= tolerable_max_lines + and input_char_count <= tolerable_max_chars + ): + return input_lines + # Truncate first to max_lines, and then truncate to max_chars if necessary + truncated_explanation = input_lines[:max_lines] + truncated_char = True + # We reevaluate the need to truncate chars following removal of some lines + if len("".join(truncated_explanation)) > tolerable_max_chars: + truncated_explanation = _truncate_by_char_count( + truncated_explanation, max_chars + ) + else: + truncated_char = False + + truncated_line_count = len(input_lines) - len(truncated_explanation) + if truncated_explanation[-1]: + # Add ellipsis and take into account part-truncated final line + truncated_explanation[-1] = truncated_explanation[-1] + "..." + if truncated_char: + # It's possible that we did not remove any char from this line + truncated_line_count += 1 + else: + # Add proper ellipsis when we were able to fit a full line exactly + truncated_explanation[-1] = "..." + return [ + *truncated_explanation, + "", + f"...Full output truncated ({truncated_line_count} line" + f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}", + ] + + +def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]: + # Find point at which input length exceeds total allowed length + iterated_char_count = 0 + for iterated_index, input_line in enumerate(input_lines): + if iterated_char_count + len(input_line) > max_chars: + break + iterated_char_count += len(input_line) + + # Create truncated explanation with modified final line + truncated_result = input_lines[:iterated_index] + final_line = input_lines[iterated_index] + if final_line: + final_line_truncate_point = max_chars - iterated_char_count + final_line = final_line[:final_line_truncate_point] + truncated_result.append(final_line) + return truncated_result diff --git a/.venv/Lib/site-packages/_pytest/assertion/util.py b/.venv/Lib/site-packages/_pytest/assertion/util.py new file mode 100644 index 0000000..4dc1af4 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/assertion/util.py @@ -0,0 +1,609 @@ +# mypy: allow-untyped-defs +"""Utilities for assertion debugging.""" + +from __future__ import annotations + +import collections.abc +import os +import pprint +from typing import AbstractSet +from typing import Any +from typing import Callable +from typing import Iterable +from typing import Literal +from typing import Mapping +from typing import Protocol +from typing import Sequence +from unicodedata import normalize + +from _pytest import outcomes +import _pytest._code +from _pytest._io.pprint import PrettyPrinter +from _pytest._io.saferepr import saferepr +from _pytest._io.saferepr import saferepr_unlimited +from _pytest.config import Config + + +# The _reprcompare attribute on the util module is used by the new assertion +# interpretation code and assertion rewriter to detect this plugin was +# loaded and in turn call the hooks defined here as part of the +# DebugInterpreter. +_reprcompare: Callable[[str, object, object], str | None] | None = None + +# Works similarly as _reprcompare attribute. Is populated with the hook call +# when pytest_runtest_setup is called. +_assertion_pass: Callable[[int, str, str], None] | None = None + +# Config object which is assigned during pytest_runtest_protocol. +_config: Config | None = None + + +class _HighlightFunc(Protocol): + def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") -> str: + """Apply highlighting to the given source.""" + + +def format_explanation(explanation: str) -> str: + r"""Format an explanation. + + Normally all embedded newlines are escaped, however there are + three exceptions: \n{, \n} and \n~. The first two are intended + cover nested explanations, see function and attribute explanations + for examples (.visit_Call(), visit_Attribute()). The last one is + for when one explanation needs to span multiple lines, e.g. when + displaying diffs. + """ + lines = _split_explanation(explanation) + result = _format_lines(lines) + return "\n".join(result) + + +def _split_explanation(explanation: str) -> list[str]: + r"""Return a list of individual lines in the explanation. + + This will return a list of lines split on '\n{', '\n}' and '\n~'. + Any other newlines will be escaped and appear in the line as the + literal '\n' characters. + """ + raw_lines = (explanation or "").split("\n") + lines = [raw_lines[0]] + for values in raw_lines[1:]: + if values and values[0] in ["{", "}", "~", ">"]: + lines.append(values) + else: + lines[-1] += "\\n" + values + return lines + + +def _format_lines(lines: Sequence[str]) -> list[str]: + """Format the individual lines. + + This will replace the '{', '}' and '~' characters of our mini formatting + language with the proper 'where ...', 'and ...' and ' + ...' text, taking + care of indentation along the way. + + Return a list of formatted lines. + """ + result = list(lines[:1]) + stack = [0] + stackcnt = [0] + for line in lines[1:]: + if line.startswith("{"): + if stackcnt[-1]: + s = "and " + else: + s = "where " + stack.append(len(result)) + stackcnt[-1] += 1 + stackcnt.append(0) + result.append(" +" + " " * (len(stack) - 1) + s + line[1:]) + elif line.startswith("}"): + stack.pop() + stackcnt.pop() + result[stack[-1]] += line[1:] + else: + assert line[0] in ["~", ">"] + stack[-1] += 1 + indent = len(stack) if line.startswith("~") else len(stack) - 1 + result.append(" " * indent + line[1:]) + assert len(stack) == 1 + return result + + +def issequence(x: Any) -> bool: + return isinstance(x, collections.abc.Sequence) and not isinstance(x, str) + + +def istext(x: Any) -> bool: + return isinstance(x, str) + + +def isdict(x: Any) -> bool: + return isinstance(x, dict) + + +def isset(x: Any) -> bool: + return isinstance(x, (set, frozenset)) + + +def isnamedtuple(obj: Any) -> bool: + return isinstance(obj, tuple) and getattr(obj, "_fields", None) is not None + + +def isdatacls(obj: Any) -> bool: + return getattr(obj, "__dataclass_fields__", None) is not None + + +def isattrs(obj: Any) -> bool: + return getattr(obj, "__attrs_attrs__", None) is not None + + +def isiterable(obj: Any) -> bool: + try: + iter(obj) + return not istext(obj) + except Exception: + return False + + +def has_default_eq( + obj: object, +) -> bool: + """Check if an instance of an object contains the default eq + + First, we check if the object's __eq__ attribute has __code__, + if so, we check the equally of the method code filename (__code__.co_filename) + to the default one generated by the dataclass and attr module + for dataclasses the default co_filename is , for attrs class, the __eq__ should contain "attrs eq generated" + """ + # inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68 + if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"): + code_filename = obj.__eq__.__code__.co_filename + + if isattrs(obj): + return "attrs generated eq" in code_filename + + return code_filename == "" # data class + return True + + +def assertrepr_compare( + config, op: str, left: Any, right: Any, use_ascii: bool = False +) -> list[str] | None: + """Return specialised explanations for some operators/operands.""" + verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + + # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. + # See issue #3246. + use_ascii = ( + isinstance(left, str) + and isinstance(right, str) + and normalize("NFD", left) == normalize("NFD", right) + ) + + if verbose > 1: + left_repr = saferepr_unlimited(left, use_ascii=use_ascii) + right_repr = saferepr_unlimited(right, use_ascii=use_ascii) + else: + # XXX: "15 chars indentation" is wrong + # ("E AssertionError: assert "); should use term width. + maxsize = ( + 80 - 15 - len(op) - 2 + ) // 2 # 15 chars indentation, 1 space around op + + left_repr = saferepr(left, maxsize=maxsize, use_ascii=use_ascii) + right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) + + summary = f"{left_repr} {op} {right_repr}" + highlighter = config.get_terminal_writer()._highlight + + explanation = None + try: + if op == "==": + explanation = _compare_eq_any(left, right, highlighter, verbose) + elif op == "not in": + if istext(left) and istext(right): + explanation = _notin_text(left, right, verbose) + elif op == "!=": + if isset(left) and isset(right): + explanation = ["Both sets are equal"] + elif op == ">=": + if isset(left) and isset(right): + explanation = _compare_gte_set(left, right, highlighter, verbose) + elif op == "<=": + if isset(left) and isset(right): + explanation = _compare_lte_set(left, right, highlighter, verbose) + elif op == ">": + if isset(left) and isset(right): + explanation = _compare_gt_set(left, right, highlighter, verbose) + elif op == "<": + if isset(left) and isset(right): + explanation = _compare_lt_set(left, right, highlighter, verbose) + + except outcomes.Exit: + raise + except Exception: + repr_crash = _pytest._code.ExceptionInfo.from_current()._getreprcrash() + explanation = [ + f"(pytest_assertion plugin: representation of details failed: {repr_crash}.", + " Probably an object has a faulty __repr__.)", + ] + + if not explanation: + return None + + if explanation[0] != "": + explanation = ["", *explanation] + return [summary, *explanation] + + +def _compare_eq_any( + left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0 +) -> list[str]: + explanation = [] + if istext(left) and istext(right): + explanation = _diff_text(left, right, verbose) + else: + from _pytest.python_api import ApproxBase + + if isinstance(left, ApproxBase) or isinstance(right, ApproxBase): + # Although the common order should be obtained == expected, this ensures both ways + approx_side = left if isinstance(left, ApproxBase) else right + other_side = right if isinstance(left, ApproxBase) else left + + explanation = approx_side._repr_compare(other_side) + elif type(left) is type(right) and ( + isdatacls(left) or isattrs(left) or isnamedtuple(left) + ): + # Note: unlike dataclasses/attrs, namedtuples compare only the + # field values, not the type or field names. But this branch + # intentionally only handles the same-type case, which was often + # used in older code bases before dataclasses/attrs were available. + explanation = _compare_eq_cls(left, right, highlighter, verbose) + elif issequence(left) and issequence(right): + explanation = _compare_eq_sequence(left, right, highlighter, verbose) + elif isset(left) and isset(right): + explanation = _compare_eq_set(left, right, highlighter, verbose) + elif isdict(left) and isdict(right): + explanation = _compare_eq_dict(left, right, highlighter, verbose) + + if isiterable(left) and isiterable(right): + expl = _compare_eq_iterable(left, right, highlighter, verbose) + explanation.extend(expl) + + return explanation + + +def _diff_text(left: str, right: str, verbose: int = 0) -> list[str]: + """Return the explanation for the diff between text. + + Unless --verbose is used this will skip leading and trailing + characters which are identical to keep the diff minimal. + """ + from difflib import ndiff + + explanation: list[str] = [] + + if verbose < 1: + i = 0 # just in case left or right has zero length + for i in range(min(len(left), len(right))): + if left[i] != right[i]: + break + if i > 42: + i -= 10 # Provide some context + explanation = [ + f"Skipping {i} identical leading characters in diff, use -v to show" + ] + left = left[i:] + right = right[i:] + if len(left) == len(right): + for i in range(len(left)): + if left[-i] != right[-i]: + break + if i > 42: + i -= 10 # Provide some context + explanation += [ + f"Skipping {i} identical trailing " + "characters in diff, use -v to show" + ] + left = left[:-i] + right = right[:-i] + keepends = True + if left.isspace() or right.isspace(): + left = repr(str(left)) + right = repr(str(right)) + explanation += ["Strings contain only whitespace, escaping them using repr()"] + # "right" is the expected base against which we compare "left", + # see https://github.com/pytest-dev/pytest/issues/3333 + explanation += [ + line.strip("\n") + for line in ndiff(right.splitlines(keepends), left.splitlines(keepends)) + ] + return explanation + + +def _compare_eq_iterable( + left: Iterable[Any], + right: Iterable[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + if verbose <= 0 and not running_on_ci(): + return ["Use -v to get more diff"] + # dynamic import to speedup pytest + import difflib + + left_formatting = PrettyPrinter().pformat(left).splitlines() + right_formatting = PrettyPrinter().pformat(right).splitlines() + + explanation = ["", "Full diff:"] + # "right" is the expected base against which we compare "left", + # see https://github.com/pytest-dev/pytest/issues/3333 + explanation.extend( + highlighter( + "\n".join( + line.rstrip() + for line in difflib.ndiff(right_formatting, left_formatting) + ), + lexer="diff", + ).splitlines() + ) + return explanation + + +def _compare_eq_sequence( + left: Sequence[Any], + right: Sequence[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) + explanation: list[str] = [] + len_left = len(left) + len_right = len(right) + for i in range(min(len_left, len_right)): + if left[i] != right[i]: + if comparing_bytes: + # when comparing bytes, we want to see their ascii representation + # instead of their numeric values (#5260) + # using a slice gives us the ascii representation: + # >>> s = b'foo' + # >>> s[0] + # 102 + # >>> s[0:1] + # b'f' + left_value = left[i : i + 1] + right_value = right[i : i + 1] + else: + left_value = left[i] + right_value = right[i] + + explanation.append( + f"At index {i} diff:" + f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}" + ) + break + + if comparing_bytes: + # when comparing bytes, it doesn't help to show the "sides contain one or more + # items" longer explanation, so skip it + + return explanation + + len_diff = len_left - len_right + if len_diff: + if len_diff > 0: + dir_with_more = "Left" + extra = saferepr(left[len_right]) + else: + len_diff = 0 - len_diff + dir_with_more = "Right" + extra = saferepr(right[len_left]) + + if len_diff == 1: + explanation += [ + f"{dir_with_more} contains one more item: {highlighter(extra)}" + ] + else: + explanation += [ + "%s contains %d more items, first extra item: %s" + % (dir_with_more, len_diff, highlighter(extra)) + ] + return explanation + + +def _compare_eq_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + explanation = [] + explanation.extend(_set_one_sided_diff("left", left, right, highlighter)) + explanation.extend(_set_one_sided_diff("right", right, left, highlighter)) + return explanation + + +def _compare_gt_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + explanation = _compare_gte_set(left, right, highlighter) + if not explanation: + return ["Both sets are equal"] + return explanation + + +def _compare_lt_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + explanation = _compare_lte_set(left, right, highlighter) + if not explanation: + return ["Both sets are equal"] + return explanation + + +def _compare_gte_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + return _set_one_sided_diff("right", right, left, highlighter) + + +def _compare_lte_set( + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + return _set_one_sided_diff("left", left, right, highlighter) + + +def _set_one_sided_diff( + posn: str, + set1: AbstractSet[Any], + set2: AbstractSet[Any], + highlighter: _HighlightFunc, +) -> list[str]: + explanation = [] + diff = set1 - set2 + if diff: + explanation.append(f"Extra items in the {posn} set:") + for item in diff: + explanation.append(highlighter(saferepr(item))) + return explanation + + +def _compare_eq_dict( + left: Mapping[Any, Any], + right: Mapping[Any, Any], + highlighter: _HighlightFunc, + verbose: int = 0, +) -> list[str]: + explanation: list[str] = [] + set_left = set(left) + set_right = set(right) + common = set_left.intersection(set_right) + same = {k: left[k] for k in common if left[k] == right[k]} + if same and verbose < 2: + explanation += [f"Omitting {len(same)} identical items, use -vv to show"] + elif same: + explanation += ["Common items:"] + explanation += highlighter(pprint.pformat(same)).splitlines() + diff = {k for k in common if left[k] != right[k]} + if diff: + explanation += ["Differing items:"] + for k in diff: + explanation += [ + highlighter(saferepr({k: left[k]})) + + " != " + + highlighter(saferepr({k: right[k]})) + ] + extra_left = set_left - set_right + len_extra_left = len(extra_left) + if len_extra_left: + explanation.append( + "Left contains %d more item%s:" + % (len_extra_left, "" if len_extra_left == 1 else "s") + ) + explanation.extend( + highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines() + ) + extra_right = set_right - set_left + len_extra_right = len(extra_right) + if len_extra_right: + explanation.append( + "Right contains %d more item%s:" + % (len_extra_right, "" if len_extra_right == 1 else "s") + ) + explanation.extend( + highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines() + ) + return explanation + + +def _compare_eq_cls( + left: Any, right: Any, highlighter: _HighlightFunc, verbose: int +) -> list[str]: + if not has_default_eq(left): + return [] + if isdatacls(left): + import dataclasses + + all_fields = dataclasses.fields(left) + fields_to_check = [info.name for info in all_fields if info.compare] + elif isattrs(left): + all_fields = left.__attrs_attrs__ + fields_to_check = [field.name for field in all_fields if getattr(field, "eq")] + elif isnamedtuple(left): + fields_to_check = left._fields + else: + assert False + + indent = " " + same = [] + diff = [] + for field in fields_to_check: + if getattr(left, field) == getattr(right, field): + same.append(field) + else: + diff.append(field) + + explanation = [] + if same or diff: + explanation += [""] + if same and verbose < 2: + explanation.append(f"Omitting {len(same)} identical items, use -vv to show") + elif same: + explanation += ["Matching attributes:"] + explanation += highlighter(pprint.pformat(same)).splitlines() + if diff: + explanation += ["Differing attributes:"] + explanation += highlighter(pprint.pformat(diff)).splitlines() + for field in diff: + field_left = getattr(left, field) + field_right = getattr(right, field) + explanation += [ + "", + f"Drill down into differing attribute {field}:", + f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}", + ] + explanation += [ + indent + line + for line in _compare_eq_any( + field_left, field_right, highlighter, verbose + ) + ] + return explanation + + +def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]: + index = text.find(term) + head = text[:index] + tail = text[index + len(term) :] + correct_text = head + tail + diff = _diff_text(text, correct_text, verbose) + newdiff = [f"{saferepr(term, maxsize=42)} is contained here:"] + for line in diff: + if line.startswith("Skipping"): + continue + if line.startswith("- "): + continue + if line.startswith("+ "): + newdiff.append(" " + line[2:]) + else: + newdiff.append(line) + return newdiff + + +def running_on_ci() -> bool: + """Check if we're currently running on a CI system.""" + env_vars = ["CI", "BUILD_NUMBER"] + return any(var in os.environ for var in env_vars) diff --git a/.venv/Lib/site-packages/_pytest/cacheprovider.py b/.venv/Lib/site-packages/_pytest/cacheprovider.py new file mode 100644 index 0000000..1b236ef --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/cacheprovider.py @@ -0,0 +1,626 @@ +# mypy: allow-untyped-defs +"""Implementation of the cache provider.""" + +# This plugin was not named "cache" to avoid conflicts with the external +# pytest-cache version. +from __future__ import annotations + +import dataclasses +import errno +import json +import os +from pathlib import Path +import tempfile +from typing import final +from typing import Generator +from typing import Iterable + +from .pathlib import resolve_from_str +from .pathlib import rm_rf +from .reports import CollectReport +from _pytest import nodes +from _pytest._io import TerminalWriter +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest +from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureRequest +from _pytest.main import Session +from _pytest.nodes import Directory +from _pytest.nodes import File +from _pytest.reports import TestReport + + +README_CONTENT = """\ +# pytest cache directory # + +This directory contains data from the pytest's cache plugin, +which provides the `--lf` and `--ff` options, as well as the `cache` fixture. + +**Do not** commit this to version control. + +See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information. +""" + +CACHEDIR_TAG_CONTENT = b"""\ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by pytest. +# For information about cache directory tags, see: +# https://bford.info/cachedir/spec.html +""" + + +@final +@dataclasses.dataclass +class Cache: + """Instance of the `cache` fixture.""" + + _cachedir: Path = dataclasses.field(repr=False) + _config: Config = dataclasses.field(repr=False) + + # Sub-directory under cache-dir for directories created by `mkdir()`. + _CACHE_PREFIX_DIRS = "d" + + # Sub-directory under cache-dir for values created by `set()`. + _CACHE_PREFIX_VALUES = "v" + + def __init__( + self, cachedir: Path, config: Config, *, _ispytest: bool = False + ) -> None: + check_ispytest(_ispytest) + self._cachedir = cachedir + self._config = config + + @classmethod + def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache: + """Create the Cache instance for a Config. + + :meta private: + """ + check_ispytest(_ispytest) + cachedir = cls.cache_dir_from_config(config, _ispytest=True) + if config.getoption("cacheclear") and cachedir.is_dir(): + cls.clear_cache(cachedir, _ispytest=True) + return cls(cachedir, config, _ispytest=True) + + @classmethod + def clear_cache(cls, cachedir: Path, _ispytest: bool = False) -> None: + """Clear the sub-directories used to hold cached directories and values. + + :meta private: + """ + check_ispytest(_ispytest) + for prefix in (cls._CACHE_PREFIX_DIRS, cls._CACHE_PREFIX_VALUES): + d = cachedir / prefix + if d.is_dir(): + rm_rf(d) + + @staticmethod + def cache_dir_from_config(config: Config, *, _ispytest: bool = False) -> Path: + """Get the path to the cache directory for a Config. + + :meta private: + """ + check_ispytest(_ispytest) + return resolve_from_str(config.getini("cache_dir"), config.rootpath) + + def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None: + """Issue a cache warning. + + :meta private: + """ + check_ispytest(_ispytest) + import warnings + + from _pytest.warning_types import PytestCacheWarning + + warnings.warn( + PytestCacheWarning(fmt.format(**args) if args else fmt), + self._config.hook, + stacklevel=3, + ) + + def _mkdir(self, path: Path) -> None: + self._ensure_cache_dir_and_supporting_files() + path.mkdir(exist_ok=True, parents=True) + + def mkdir(self, name: str) -> Path: + """Return a directory path object with the given name. + + If the directory does not yet exist, it will be created. You can use + it to manage files to e.g. store/retrieve database dumps across test + sessions. + + .. versionadded:: 7.0 + + :param name: + Must be a string not containing a ``/`` separator. + Make sure the name contains your plugin or application + identifiers to prevent clashes with other cache users. + """ + path = Path(name) + if len(path.parts) > 1: + raise ValueError("name is not allowed to contain path separators") + res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path) + self._mkdir(res) + return res + + def _getvaluepath(self, key: str) -> Path: + return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key)) + + def get(self, key: str, default): + """Return the cached value for the given key. + + If no value was yet cached or the value cannot be read, the specified + default is returned. + + :param key: + Must be a ``/`` separated value. Usually the first + name is the name of your plugin or your application. + :param default: + The value to return in case of a cache-miss or invalid cache value. + """ + path = self._getvaluepath(key) + try: + with path.open("r", encoding="UTF-8") as f: + return json.load(f) + except (ValueError, OSError): + return default + + def set(self, key: str, value: object) -> None: + """Save value for the given key. + + :param key: + Must be a ``/`` separated value. Usually the first + name is the name of your plugin or your application. + :param value: + Must be of any combination of basic python types, + including nested types like lists of dictionaries. + """ + path = self._getvaluepath(key) + try: + self._mkdir(path.parent) + except OSError as exc: + self.warn( + f"could not create cache path {path}: {exc}", + _ispytest=True, + ) + return + data = json.dumps(value, ensure_ascii=False, indent=2) + try: + f = path.open("w", encoding="UTF-8") + except OSError as exc: + self.warn( + f"cache could not write path {path}: {exc}", + _ispytest=True, + ) + else: + with f: + f.write(data) + + def _ensure_cache_dir_and_supporting_files(self) -> None: + """Create the cache dir and its supporting files.""" + if self._cachedir.is_dir(): + return + + self._cachedir.parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory( + prefix="pytest-cache-files-", + dir=self._cachedir.parent, + ) as newpath: + path = Path(newpath) + + # Reset permissions to the default, see #12308. + # Note: there's no way to get the current umask atomically, eek. + umask = os.umask(0o022) + os.umask(umask) + path.chmod(0o777 - umask) + + with open(path.joinpath("README.md"), "x", encoding="UTF-8") as f: + f.write(README_CONTENT) + with open(path.joinpath(".gitignore"), "x", encoding="UTF-8") as f: + f.write("# Created by pytest automatically.\n*\n") + with open(path.joinpath("CACHEDIR.TAG"), "xb") as f: + f.write(CACHEDIR_TAG_CONTENT) + + try: + path.rename(self._cachedir) + except OSError as e: + # If 2 concurrent pytests both race to the rename, the loser + # gets "Directory not empty" from the rename. In this case, + # everything is handled so just continue (while letting the + # temporary directory be cleaned up). + # On Windows, the error is a FileExistsError which translates to EEXIST. + if e.errno not in (errno.ENOTEMPTY, errno.EEXIST): + raise + else: + # Create a directory in place of the one we just moved so that + # `TemporaryDirectory`'s cleanup doesn't complain. + # + # TODO: pass ignore_cleanup_errors=True when we no longer support python < 3.10. + # See https://github.com/python/cpython/issues/74168. Note that passing + # delete=False would do the wrong thing in case of errors and isn't supported + # until python 3.12. + path.mkdir() + + +class LFPluginCollWrapper: + def __init__(self, lfplugin: LFPlugin) -> None: + self.lfplugin = lfplugin + self._collected_at_least_one_failure = False + + @hookimpl(wrapper=True) + def pytest_make_collect_report( + self, collector: nodes.Collector + ) -> Generator[None, CollectReport, CollectReport]: + res = yield + if isinstance(collector, (Session, Directory)): + # Sort any lf-paths to the beginning. + lf_paths = self.lfplugin._last_failed_paths + + # Use stable sort to prioritize last failed. + def sort_key(node: nodes.Item | nodes.Collector) -> bool: + return node.path in lf_paths + + res.result = sorted( + res.result, + key=sort_key, + reverse=True, + ) + + elif isinstance(collector, File): + if collector.path in self.lfplugin._last_failed_paths: + result = res.result + lastfailed = self.lfplugin.lastfailed + + # Only filter with known failures. + if not self._collected_at_least_one_failure: + if not any(x.nodeid in lastfailed for x in result): + return res + self.lfplugin.config.pluginmanager.register( + LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip" + ) + self._collected_at_least_one_failure = True + + session = collector.session + result[:] = [ + x + for x in result + if x.nodeid in lastfailed + # Include any passed arguments (not trivial to filter). + or session.isinitpath(x.path) + # Keep all sub-collectors. + or isinstance(x, nodes.Collector) + ] + + return res + + +class LFPluginCollSkipfiles: + def __init__(self, lfplugin: LFPlugin) -> None: + self.lfplugin = lfplugin + + @hookimpl + def pytest_make_collect_report( + self, collector: nodes.Collector + ) -> CollectReport | None: + if isinstance(collector, File): + if collector.path not in self.lfplugin._last_failed_paths: + self.lfplugin._skipped_files += 1 + + return CollectReport( + collector.nodeid, "passed", longrepr=None, result=[] + ) + return None + + +class LFPlugin: + """Plugin which implements the --lf (run last-failing) option.""" + + def __init__(self, config: Config) -> None: + self.config = config + active_keys = "lf", "failedfirst" + self.active = any(config.getoption(key) for key in active_keys) + assert config.cache + self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {}) + self._previously_failed_count: int | None = None + self._report_status: str | None = None + self._skipped_files = 0 # count skipped files during collection due to --lf + + if config.getoption("lf"): + self._last_failed_paths = self.get_last_failed_paths() + config.pluginmanager.register( + LFPluginCollWrapper(self), "lfplugin-collwrapper" + ) + + def get_last_failed_paths(self) -> set[Path]: + """Return a set with all Paths of the previously failed nodeids and + their parents.""" + rootpath = self.config.rootpath + result = set() + for nodeid in self.lastfailed: + path = rootpath / nodeid.split("::")[0] + result.add(path) + result.update(path.parents) + return {x for x in result if x.exists()} + + def pytest_report_collectionfinish(self) -> str | None: + if self.active and self.config.get_verbosity() >= 0: + return f"run-last-failure: {self._report_status}" + return None + + def pytest_runtest_logreport(self, report: TestReport) -> None: + if (report.when == "call" and report.passed) or report.skipped: + self.lastfailed.pop(report.nodeid, None) + elif report.failed: + self.lastfailed[report.nodeid] = True + + def pytest_collectreport(self, report: CollectReport) -> None: + passed = report.outcome in ("passed", "skipped") + if passed: + if report.nodeid in self.lastfailed: + self.lastfailed.pop(report.nodeid) + self.lastfailed.update((item.nodeid, True) for item in report.result) + else: + self.lastfailed[report.nodeid] = True + + @hookimpl(wrapper=True, tryfirst=True) + def pytest_collection_modifyitems( + self, config: Config, items: list[nodes.Item] + ) -> Generator[None]: + res = yield + + if not self.active: + return res + + if self.lastfailed: + previously_failed = [] + previously_passed = [] + for item in items: + if item.nodeid in self.lastfailed: + previously_failed.append(item) + else: + previously_passed.append(item) + self._previously_failed_count = len(previously_failed) + + if not previously_failed: + # Running a subset of all tests with recorded failures + # only outside of it. + self._report_status = "%d known failures not in selected tests" % ( + len(self.lastfailed), + ) + else: + if self.config.getoption("lf"): + items[:] = previously_failed + config.hook.pytest_deselected(items=previously_passed) + else: # --failedfirst + items[:] = previously_failed + previously_passed + + noun = "failure" if self._previously_failed_count == 1 else "failures" + suffix = " first" if self.config.getoption("failedfirst") else "" + self._report_status = ( + f"rerun previous {self._previously_failed_count} {noun}{suffix}" + ) + + if self._skipped_files > 0: + files_noun = "file" if self._skipped_files == 1 else "files" + self._report_status += f" (skipped {self._skipped_files} {files_noun})" + else: + self._report_status = "no previously failed tests, " + if self.config.getoption("last_failed_no_failures") == "none": + self._report_status += "deselecting all items." + config.hook.pytest_deselected(items=items[:]) + items[:] = [] + else: + self._report_status += "not deselecting items." + + return res + + def pytest_sessionfinish(self, session: Session) -> None: + config = self.config + if config.getoption("cacheshow") or hasattr(config, "workerinput"): + return + + assert config.cache is not None + saved_lastfailed = config.cache.get("cache/lastfailed", {}) + if saved_lastfailed != self.lastfailed: + config.cache.set("cache/lastfailed", self.lastfailed) + + +class NFPlugin: + """Plugin which implements the --nf (run new-first) option.""" + + def __init__(self, config: Config) -> None: + self.config = config + self.active = config.option.newfirst + assert config.cache is not None + self.cached_nodeids = set(config.cache.get("cache/nodeids", [])) + + @hookimpl(wrapper=True, tryfirst=True) + def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> Generator[None]: + res = yield + + if self.active: + new_items: dict[str, nodes.Item] = {} + other_items: dict[str, nodes.Item] = {} + for item in items: + if item.nodeid not in self.cached_nodeids: + new_items[item.nodeid] = item + else: + other_items[item.nodeid] = item + + items[:] = self._get_increasing_order( + new_items.values() + ) + self._get_increasing_order(other_items.values()) + self.cached_nodeids.update(new_items) + else: + self.cached_nodeids.update(item.nodeid for item in items) + + return res + + def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]: + return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) + + def pytest_sessionfinish(self) -> None: + config = self.config + if config.getoption("cacheshow") or hasattr(config, "workerinput"): + return + + if config.getoption("collectonly"): + return + + assert config.cache is not None + config.cache.set("cache/nodeids", sorted(self.cached_nodeids)) + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("general") + group.addoption( + "--lf", + "--last-failed", + action="store_true", + dest="lf", + help="Rerun only the tests that failed " + "at the last run (or all if none failed)", + ) + group.addoption( + "--ff", + "--failed-first", + action="store_true", + dest="failedfirst", + help="Run all tests, but run the last failures first. " + "This may re-order tests and thus lead to " + "repeated fixture setup/teardown.", + ) + group.addoption( + "--nf", + "--new-first", + action="store_true", + dest="newfirst", + help="Run tests from new files first, then the rest of the tests " + "sorted by file mtime", + ) + group.addoption( + "--cache-show", + action="append", + nargs="?", + dest="cacheshow", + help=( + "Show cache contents, don't perform collection or tests. " + "Optional argument: glob (default: '*')." + ), + ) + group.addoption( + "--cache-clear", + action="store_true", + dest="cacheclear", + help="Remove all cache contents at start of test run", + ) + cache_dir_default = ".pytest_cache" + if "TOX_ENV_DIR" in os.environ: + cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default) + parser.addini("cache_dir", default=cache_dir_default, help="Cache directory path") + group.addoption( + "--lfnf", + "--last-failed-no-failures", + action="store", + dest="last_failed_no_failures", + choices=("all", "none"), + default="all", + help="With ``--lf``, determines whether to execute tests when there " + "are no previously (known) failures or when no " + "cached ``lastfailed`` data was found. " + "``all`` (the default) runs the full test suite again. " + "``none`` just emits a message about no known failures and exits successfully.", + ) + + +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: + if config.option.cacheshow and not config.option.help: + from _pytest.main import wrap_session + + return wrap_session(config, cacheshow) + return None + + +@hookimpl(tryfirst=True) +def pytest_configure(config: Config) -> None: + config.cache = Cache.for_config(config, _ispytest=True) + config.pluginmanager.register(LFPlugin(config), "lfplugin") + config.pluginmanager.register(NFPlugin(config), "nfplugin") + + +@fixture +def cache(request: FixtureRequest) -> Cache: + """Return a cache object that can persist state between testing sessions. + + cache.get(key, default) + cache.set(key, value) + + Keys must be ``/`` separated strings, where the first part is usually the + name of your plugin or application to avoid clashes with other cache users. + + Values can be any object handled by the json stdlib module. + """ + assert request.config.cache is not None + return request.config.cache + + +def pytest_report_header(config: Config) -> str | None: + """Display cachedir with --cache-show and if non-default.""" + if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache": + assert config.cache is not None + cachedir = config.cache._cachedir + # TODO: evaluate generating upward relative paths + # starting with .., ../.. if sensible + + try: + displaypath = cachedir.relative_to(config.rootpath) + except ValueError: + displaypath = cachedir + return f"cachedir: {displaypath}" + return None + + +def cacheshow(config: Config, session: Session) -> int: + from pprint import pformat + + assert config.cache is not None + + tw = TerminalWriter() + tw.line("cachedir: " + str(config.cache._cachedir)) + if not config.cache._cachedir.is_dir(): + tw.line("cache is empty") + return 0 + + glob = config.option.cacheshow[0] + if glob is None: + glob = "*" + + dummy = object() + basedir = config.cache._cachedir + vdir = basedir / Cache._CACHE_PREFIX_VALUES + tw.sep("-", f"cache values for {glob!r}") + for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): + key = str(valpath.relative_to(vdir)) + val = config.cache.get(key, dummy) + if val is dummy: + tw.line(f"{key} contains unreadable content, will be ignored") + else: + tw.line(f"{key} contains:") + for line in pformat(val).splitlines(): + tw.line(" " + line) + + ddir = basedir / Cache._CACHE_PREFIX_DIRS + if ddir.is_dir(): + contents = sorted(ddir.rglob(glob)) + tw.sep("-", f"cache directories for {glob!r}") + for p in contents: + # if p.is_dir(): + # print("%s/" % p.relative_to(basedir)) + if p.is_file(): + key = str(p.relative_to(basedir)) + tw.line(f"{key} is a file of length {p.stat().st_size:d}") + return 0 diff --git a/.venv/Lib/site-packages/_pytest/capture.py b/.venv/Lib/site-packages/_pytest/capture.py new file mode 100644 index 0000000..ded8af8 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/capture.py @@ -0,0 +1,1105 @@ +# mypy: allow-untyped-defs +"""Per-test stdout/stderr capturing mechanism.""" + +from __future__ import annotations + +import abc +import collections +import contextlib +import io +from io import UnsupportedOperation +import os +import sys +from tempfile import TemporaryFile +from types import TracebackType +from typing import Any +from typing import AnyStr +from typing import BinaryIO +from typing import Final +from typing import final +from typing import Generator +from typing import Generic +from typing import Iterable +from typing import Iterator +from typing import Literal +from typing import NamedTuple +from typing import TextIO +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from typing_extensions import Self + +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest +from _pytest.fixtures import fixture +from _pytest.fixtures import SubRequest +from _pytest.nodes import Collector +from _pytest.nodes import File +from _pytest.nodes import Item +from _pytest.reports import CollectReport + + +_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("general") + group._addoption( + "--capture", + action="store", + default="fd", + metavar="method", + choices=["fd", "sys", "no", "tee-sys"], + help="Per-test capturing method: one of fd|sys|no|tee-sys", + ) + group._addoption( + "-s", + action="store_const", + const="no", + dest="capture", + help="Shortcut for --capture=no", + ) + + +def _colorama_workaround() -> None: + """Ensure colorama is imported so that it attaches to the correct stdio + handles on Windows. + + colorama uses the terminal on import time. So if something does the + first import of colorama while I/O capture is active, colorama will + fail in various ways. + """ + if sys.platform.startswith("win32"): + try: + import colorama # noqa: F401 + except ImportError: + pass + + +def _readline_workaround() -> None: + """Ensure readline is imported early so it attaches to the correct stdio handles. + + This isn't a problem with the default GNU readline implementation, but in + some configurations, Python uses libedit instead (on macOS, and for prebuilt + binaries such as used by uv). + + In theory this is only needed if readline.backend == "libedit", but the + workaround consists of importing readline here, so we already worked around + the issue by the time we could check if we need to. + """ + try: + import readline # noqa: F401 + except ImportError: + pass + + +def _windowsconsoleio_workaround(stream: TextIO) -> None: + """Workaround for Windows Unicode console handling. + + Python 3.6 implemented Unicode console handling for Windows. This works + by reading/writing to the raw console handle using + ``{Read,Write}ConsoleW``. + + The problem is that we are going to ``dup2`` over the stdio file + descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the + handles used by Python to write to the console. Though there is still some + weirdness and the console handle seems to only be closed randomly and not + on the first call to ``CloseHandle``, or maybe it gets reopened with the + same handle value when we suspend capturing. + + The workaround in this case will reopen stdio with a different fd which + also means a different handle by replicating the logic in + "Py_lifecycle.c:initstdio/create_stdio". + + :param stream: + In practice ``sys.stdout`` or ``sys.stderr``, but given + here as parameter for unittesting purposes. + + See https://github.com/pytest-dev/py/issues/103. + """ + if not sys.platform.startswith("win32") or hasattr(sys, "pypy_version_info"): + return + + # Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666). + if not hasattr(stream, "buffer"): # type: ignore[unreachable,unused-ignore] + return + + raw_stdout = stream.buffer.raw if hasattr(stream.buffer, "raw") else stream.buffer + + if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined,unused-ignore] + return + + def _reopen_stdio(f, mode): + if not hasattr(stream.buffer, "raw") and mode[0] == "w": + buffering = 0 + else: + buffering = -1 + + return io.TextIOWrapper( + open(os.dup(f.fileno()), mode, buffering), + f.encoding, + f.errors, + f.newlines, + f.line_buffering, + ) + + sys.stdin = _reopen_stdio(sys.stdin, "rb") + sys.stdout = _reopen_stdio(sys.stdout, "wb") + sys.stderr = _reopen_stdio(sys.stderr, "wb") + + +@hookimpl(wrapper=True) +def pytest_load_initial_conftests(early_config: Config) -> Generator[None]: + ns = early_config.known_args_namespace + if ns.capture == "fd": + _windowsconsoleio_workaround(sys.stdout) + _colorama_workaround() + _readline_workaround() + pluginmanager = early_config.pluginmanager + capman = CaptureManager(ns.capture) + pluginmanager.register(capman, "capturemanager") + + # Make sure that capturemanager is properly reset at final shutdown. + early_config.add_cleanup(capman.stop_global_capturing) + + # Finally trigger conftest loading but while capturing (issue #93). + capman.start_global_capturing() + try: + try: + yield + finally: + capman.suspend_global_capture() + except BaseException: + out, err = capman.read_global_capture() + sys.stdout.write(out) + sys.stderr.write(err) + raise + + +# IO Helpers. + + +class EncodedFile(io.TextIOWrapper): + __slots__ = () + + @property + def name(self) -> str: + # Ensure that file.name is a string. Workaround for a Python bug + # fixed in >=3.7.4: https://bugs.python.org/issue36015 + return repr(self.buffer) + + @property + def mode(self) -> str: + # TextIOWrapper doesn't expose a mode, but at least some of our + # tests check it. + return self.buffer.mode.replace("b", "") + + +class CaptureIO(io.TextIOWrapper): + def __init__(self) -> None: + super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True) + + def getvalue(self) -> str: + assert isinstance(self.buffer, io.BytesIO) + return self.buffer.getvalue().decode("UTF-8") + + +class TeeCaptureIO(CaptureIO): + def __init__(self, other: TextIO) -> None: + self._other = other + super().__init__() + + def write(self, s: str) -> int: + super().write(s) + return self._other.write(s) + + +class DontReadFromInput(TextIO): + @property + def encoding(self) -> str: + assert sys.__stdin__ is not None + return sys.__stdin__.encoding + + def read(self, size: int = -1) -> str: + raise OSError( + "pytest: reading from stdin while output is captured! Consider using `-s`." + ) + + readline = read + + def __next__(self) -> str: + return self.readline() + + def readlines(self, hint: int | None = -1) -> list[str]: + raise OSError( + "pytest: reading from stdin while output is captured! Consider using `-s`." + ) + + def __iter__(self) -> Iterator[str]: + return self + + def fileno(self) -> int: + raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()") + + def flush(self) -> None: + raise UnsupportedOperation("redirected stdin is pseudofile, has no flush()") + + def isatty(self) -> bool: + return False + + def close(self) -> None: + pass + + def readable(self) -> bool: + return False + + def seek(self, offset: int, whence: int = 0) -> int: + raise UnsupportedOperation("redirected stdin is pseudofile, has no seek(int)") + + def seekable(self) -> bool: + return False + + def tell(self) -> int: + raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") + + def truncate(self, size: int | None = None) -> int: + raise UnsupportedOperation("cannot truncate stdin") + + def write(self, data: str) -> int: + raise UnsupportedOperation("cannot write to stdin") + + def writelines(self, lines: Iterable[str]) -> None: + raise UnsupportedOperation("Cannot write to stdin") + + def writable(self) -> bool: + return False + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + type: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + pass + + @property + def buffer(self) -> BinaryIO: + # The str/bytes doesn't actually matter in this type, so OK to fake. + return self # type: ignore[return-value] + + +# Capture classes. + + +class CaptureBase(abc.ABC, Generic[AnyStr]): + EMPTY_BUFFER: AnyStr + + @abc.abstractmethod + def __init__(self, fd: int) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def start(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def done(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def suspend(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def resume(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def writeorg(self, data: AnyStr) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def snap(self) -> AnyStr: + raise NotImplementedError() + + +patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} + + +class NoCapture(CaptureBase[str]): + EMPTY_BUFFER = "" + + def __init__(self, fd: int) -> None: + pass + + def start(self) -> None: + pass + + def done(self) -> None: + pass + + def suspend(self) -> None: + pass + + def resume(self) -> None: + pass + + def snap(self) -> str: + return "" + + def writeorg(self, data: str) -> None: + pass + + +class SysCaptureBase(CaptureBase[AnyStr]): + def __init__( + self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False + ) -> None: + name = patchsysdict[fd] + self._old: TextIO = getattr(sys, name) + self.name = name + if tmpfile is None: + if name == "stdin": + tmpfile = DontReadFromInput() + else: + tmpfile = CaptureIO() if not tee else TeeCaptureIO(self._old) + self.tmpfile = tmpfile + self._state = "initialized" + + def repr(self, class_name: str) -> str: + return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( + class_name, + self.name, + hasattr(self, "_old") and repr(self._old) or "", + self._state, + self.tmpfile, + ) + + def __repr__(self) -> str: + return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( + self.__class__.__name__, + self.name, + hasattr(self, "_old") and repr(self._old) or "", + self._state, + self.tmpfile, + ) + + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: + assert ( + self._state in states + ), "cannot {} in state {!r}: expected one of {}".format( + op, self._state, ", ".join(states) + ) + + def start(self) -> None: + self._assert_state("start", ("initialized",)) + setattr(sys, self.name, self.tmpfile) + self._state = "started" + + def done(self) -> None: + self._assert_state("done", ("initialized", "started", "suspended", "done")) + if self._state == "done": + return + setattr(sys, self.name, self._old) + del self._old + self.tmpfile.close() + self._state = "done" + + def suspend(self) -> None: + self._assert_state("suspend", ("started", "suspended")) + setattr(sys, self.name, self._old) + self._state = "suspended" + + def resume(self) -> None: + self._assert_state("resume", ("started", "suspended")) + if self._state == "started": + return + setattr(sys, self.name, self.tmpfile) + self._state = "started" + + +class SysCaptureBinary(SysCaptureBase[bytes]): + EMPTY_BUFFER = b"" + + def snap(self) -> bytes: + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.buffer.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def writeorg(self, data: bytes) -> None: + self._assert_state("writeorg", ("started", "suspended")) + self._old.flush() + self._old.buffer.write(data) + self._old.buffer.flush() + + +class SysCapture(SysCaptureBase[str]): + EMPTY_BUFFER = "" + + def snap(self) -> str: + self._assert_state("snap", ("started", "suspended")) + assert isinstance(self.tmpfile, CaptureIO) + res = self.tmpfile.getvalue() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def writeorg(self, data: str) -> None: + self._assert_state("writeorg", ("started", "suspended")) + self._old.write(data) + self._old.flush() + + +class FDCaptureBase(CaptureBase[AnyStr]): + def __init__(self, targetfd: int) -> None: + self.targetfd = targetfd + + try: + os.fstat(targetfd) + except OSError: + # FD capturing is conceptually simple -- create a temporary file, + # redirect the FD to it, redirect back when done. But when the + # target FD is invalid it throws a wrench into this lovely scheme. + # + # Tests themselves shouldn't care if the FD is valid, FD capturing + # should work regardless of external circumstances. So falling back + # to just sys capturing is not a good option. + # + # Further complications are the need to support suspend() and the + # possibility of FD reuse (e.g. the tmpfile getting the very same + # target FD). The following approach is robust, I believe. + self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR) + os.dup2(self.targetfd_invalid, targetfd) + else: + self.targetfd_invalid = None + self.targetfd_save = os.dup(targetfd) + + if targetfd == 0: + self.tmpfile = open(os.devnull, encoding="utf-8") + self.syscapture: CaptureBase[str] = SysCapture(targetfd) + else: + self.tmpfile = EncodedFile( + TemporaryFile(buffering=0), + encoding="utf-8", + errors="replace", + newline="", + write_through=True, + ) + if targetfd in patchsysdict: + self.syscapture = SysCapture(targetfd, self.tmpfile) + else: + self.syscapture = NoCapture(targetfd) + + self._state = "initialized" + + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} {self.targetfd} oldfd={self.targetfd_save} " + f"_state={self._state!r} tmpfile={self.tmpfile!r}>" + ) + + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: + assert ( + self._state in states + ), "cannot {} in state {!r}: expected one of {}".format( + op, self._state, ", ".join(states) + ) + + def start(self) -> None: + """Start capturing on targetfd using memorized tmpfile.""" + self._assert_state("start", ("initialized",)) + os.dup2(self.tmpfile.fileno(), self.targetfd) + self.syscapture.start() + self._state = "started" + + def done(self) -> None: + """Stop capturing, restore streams, return original capture file, + seeked to position zero.""" + self._assert_state("done", ("initialized", "started", "suspended", "done")) + if self._state == "done": + return + os.dup2(self.targetfd_save, self.targetfd) + os.close(self.targetfd_save) + if self.targetfd_invalid is not None: + if self.targetfd_invalid != self.targetfd: + os.close(self.targetfd) + os.close(self.targetfd_invalid) + self.syscapture.done() + self.tmpfile.close() + self._state = "done" + + def suspend(self) -> None: + self._assert_state("suspend", ("started", "suspended")) + if self._state == "suspended": + return + self.syscapture.suspend() + os.dup2(self.targetfd_save, self.targetfd) + self._state = "suspended" + + def resume(self) -> None: + self._assert_state("resume", ("started", "suspended")) + if self._state == "started": + return + self.syscapture.resume() + os.dup2(self.tmpfile.fileno(), self.targetfd) + self._state = "started" + + +class FDCaptureBinary(FDCaptureBase[bytes]): + """Capture IO to/from a given OS-level file descriptor. + + snap() produces `bytes`. + """ + + EMPTY_BUFFER = b"" + + def snap(self) -> bytes: + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.buffer.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def writeorg(self, data: bytes) -> None: + """Write to original file descriptor.""" + self._assert_state("writeorg", ("started", "suspended")) + os.write(self.targetfd_save, data) + + +class FDCapture(FDCaptureBase[str]): + """Capture IO to/from a given OS-level file descriptor. + + snap() produces text. + """ + + EMPTY_BUFFER = "" + + def snap(self) -> str: + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def writeorg(self, data: str) -> None: + """Write to original file descriptor.""" + self._assert_state("writeorg", ("started", "suspended")) + # XXX use encoding of original stream + os.write(self.targetfd_save, data.encode("utf-8")) + + +# MultiCapture + + +# Generic NamedTuple only supported since Python 3.11. +if sys.version_info >= (3, 11) or TYPE_CHECKING: + + @final + class CaptureResult(NamedTuple, Generic[AnyStr]): + """The result of :method:`caplog.readouterr() `.""" + + out: AnyStr + err: AnyStr + +else: + + class CaptureResult( + collections.namedtuple("CaptureResult", ["out", "err"]), # noqa: PYI024 + Generic[AnyStr], + ): + """The result of :method:`caplog.readouterr() `.""" + + __slots__ = () + + +class MultiCapture(Generic[AnyStr]): + _state = None + _in_suspended = False + + def __init__( + self, + in_: CaptureBase[AnyStr] | None, + out: CaptureBase[AnyStr] | None, + err: CaptureBase[AnyStr] | None, + ) -> None: + self.in_: CaptureBase[AnyStr] | None = in_ + self.out: CaptureBase[AnyStr] | None = out + self.err: CaptureBase[AnyStr] | None = err + + def __repr__(self) -> str: + return ( + f"" + ) + + def start_capturing(self) -> None: + self._state = "started" + if self.in_: + self.in_.start() + if self.out: + self.out.start() + if self.err: + self.err.start() + + def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]: + """Pop current snapshot out/err capture and flush to orig streams.""" + out, err = self.readouterr() + if out: + assert self.out is not None + self.out.writeorg(out) + if err: + assert self.err is not None + self.err.writeorg(err) + return out, err + + def suspend_capturing(self, in_: bool = False) -> None: + self._state = "suspended" + if self.out: + self.out.suspend() + if self.err: + self.err.suspend() + if in_ and self.in_: + self.in_.suspend() + self._in_suspended = True + + def resume_capturing(self) -> None: + self._state = "started" + if self.out: + self.out.resume() + if self.err: + self.err.resume() + if self._in_suspended: + assert self.in_ is not None + self.in_.resume() + self._in_suspended = False + + def stop_capturing(self) -> None: + """Stop capturing and reset capturing streams.""" + if self._state == "stopped": + raise ValueError("was already stopped") + self._state = "stopped" + if self.out: + self.out.done() + if self.err: + self.err.done() + if self.in_: + self.in_.done() + + def is_started(self) -> bool: + """Whether actively capturing -- not suspended or stopped.""" + return self._state == "started" + + def readouterr(self) -> CaptureResult[AnyStr]: + out = self.out.snap() if self.out else "" + err = self.err.snap() if self.err else "" + # TODO: This type error is real, need to fix. + return CaptureResult(out, err) # type: ignore[arg-type] + + +def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]: + if method == "fd": + return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2)) + elif method == "sys": + return MultiCapture(in_=SysCapture(0), out=SysCapture(1), err=SysCapture(2)) + elif method == "no": + return MultiCapture(in_=None, out=None, err=None) + elif method == "tee-sys": + return MultiCapture( + in_=None, out=SysCapture(1, tee=True), err=SysCapture(2, tee=True) + ) + raise ValueError(f"unknown capturing method: {method!r}") + + +# CaptureManager and CaptureFixture + + +class CaptureManager: + """The capture plugin. + + Manages that the appropriate capture method is enabled/disabled during + collection and each test phase (setup, call, teardown). After each of + those points, the captured output is obtained and attached to the + collection/runtest report. + + There are two levels of capture: + + * global: enabled by default and can be suppressed by the ``-s`` + option. This is always enabled/disabled during collection and each test + phase. + + * fixture: when a test function or one of its fixture depend on the + ``capsys`` or ``capfd`` fixtures. In this case special handling is + needed to ensure the fixtures take precedence over the global capture. + """ + + def __init__(self, method: _CaptureMethod) -> None: + self._method: Final = method + self._global_capturing: MultiCapture[str] | None = None + self._capture_fixture: CaptureFixture[Any] | None = None + + def __repr__(self) -> str: + return ( + f"" + ) + + def is_capturing(self) -> str | bool: + if self.is_globally_capturing(): + return "global" + if self._capture_fixture: + return f"fixture {self._capture_fixture.request.fixturename}" + return False + + # Global capturing control + + def is_globally_capturing(self) -> bool: + return self._method != "no" + + def start_global_capturing(self) -> None: + assert self._global_capturing is None + self._global_capturing = _get_multicapture(self._method) + self._global_capturing.start_capturing() + + def stop_global_capturing(self) -> None: + if self._global_capturing is not None: + self._global_capturing.pop_outerr_to_orig() + self._global_capturing.stop_capturing() + self._global_capturing = None + + def resume_global_capture(self) -> None: + # During teardown of the python process, and on rare occasions, capture + # attributes can be `None` while trying to resume global capture. + if self._global_capturing is not None: + self._global_capturing.resume_capturing() + + def suspend_global_capture(self, in_: bool = False) -> None: + if self._global_capturing is not None: + self._global_capturing.suspend_capturing(in_=in_) + + def suspend(self, in_: bool = False) -> None: + # Need to undo local capsys-et-al if it exists before disabling global capture. + self.suspend_fixture() + self.suspend_global_capture(in_) + + def resume(self) -> None: + self.resume_global_capture() + self.resume_fixture() + + def read_global_capture(self) -> CaptureResult[str]: + assert self._global_capturing is not None + return self._global_capturing.readouterr() + + # Fixture Control + + def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None: + if self._capture_fixture: + current_fixture = self._capture_fixture.request.fixturename + requested_fixture = capture_fixture.request.fixturename + capture_fixture.request.raiseerror( + f"cannot use {requested_fixture} and {current_fixture} at the same time" + ) + self._capture_fixture = capture_fixture + + def unset_fixture(self) -> None: + self._capture_fixture = None + + def activate_fixture(self) -> None: + """If the current item is using ``capsys`` or ``capfd``, activate + them so they take precedence over the global capture.""" + if self._capture_fixture: + self._capture_fixture._start() + + def deactivate_fixture(self) -> None: + """Deactivate the ``capsys`` or ``capfd`` fixture of this item, if any.""" + if self._capture_fixture: + self._capture_fixture.close() + + def suspend_fixture(self) -> None: + if self._capture_fixture: + self._capture_fixture._suspend() + + def resume_fixture(self) -> None: + if self._capture_fixture: + self._capture_fixture._resume() + + # Helper context managers + + @contextlib.contextmanager + def global_and_fixture_disabled(self) -> Generator[None]: + """Context manager to temporarily disable global and current fixture capturing.""" + do_fixture = self._capture_fixture and self._capture_fixture._is_started() + if do_fixture: + self.suspend_fixture() + do_global = self._global_capturing and self._global_capturing.is_started() + if do_global: + self.suspend_global_capture() + try: + yield + finally: + if do_global: + self.resume_global_capture() + if do_fixture: + self.resume_fixture() + + @contextlib.contextmanager + def item_capture(self, when: str, item: Item) -> Generator[None]: + self.resume_global_capture() + self.activate_fixture() + try: + yield + finally: + self.deactivate_fixture() + self.suspend_global_capture(in_=False) + + out, err = self.read_global_capture() + item.add_report_section(when, "stdout", out) + item.add_report_section(when, "stderr", err) + + # Hooks + + @hookimpl(wrapper=True) + def pytest_make_collect_report( + self, collector: Collector + ) -> Generator[None, CollectReport, CollectReport]: + if isinstance(collector, File): + self.resume_global_capture() + try: + rep = yield + finally: + self.suspend_global_capture() + out, err = self.read_global_capture() + if out: + rep.sections.append(("Captured stdout", out)) + if err: + rep.sections.append(("Captured stderr", err)) + else: + rep = yield + return rep + + @hookimpl(wrapper=True) + def pytest_runtest_setup(self, item: Item) -> Generator[None]: + with self.item_capture("setup", item): + return (yield) + + @hookimpl(wrapper=True) + def pytest_runtest_call(self, item: Item) -> Generator[None]: + with self.item_capture("call", item): + return (yield) + + @hookimpl(wrapper=True) + def pytest_runtest_teardown(self, item: Item) -> Generator[None]: + with self.item_capture("teardown", item): + return (yield) + + @hookimpl(tryfirst=True) + def pytest_keyboard_interrupt(self) -> None: + self.stop_global_capturing() + + @hookimpl(tryfirst=True) + def pytest_internalerror(self) -> None: + self.stop_global_capturing() + + +class CaptureFixture(Generic[AnyStr]): + """Object returned by the :fixture:`capsys`, :fixture:`capsysbinary`, + :fixture:`capfd` and :fixture:`capfdbinary` fixtures.""" + + def __init__( + self, + captureclass: type[CaptureBase[AnyStr]], + request: SubRequest, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self.captureclass: type[CaptureBase[AnyStr]] = captureclass + self.request = request + self._capture: MultiCapture[AnyStr] | None = None + self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER + self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER + + def _start(self) -> None: + if self._capture is None: + self._capture = MultiCapture( + in_=None, + out=self.captureclass(1), + err=self.captureclass(2), + ) + self._capture.start_capturing() + + def close(self) -> None: + if self._capture is not None: + out, err = self._capture.pop_outerr_to_orig() + self._captured_out += out + self._captured_err += err + self._capture.stop_capturing() + self._capture = None + + def readouterr(self) -> CaptureResult[AnyStr]: + """Read and return the captured output so far, resetting the internal + buffer. + + :returns: + The captured content as a namedtuple with ``out`` and ``err`` + string attributes. + """ + captured_out, captured_err = self._captured_out, self._captured_err + if self._capture is not None: + out, err = self._capture.readouterr() + captured_out += out + captured_err += err + self._captured_out = self.captureclass.EMPTY_BUFFER + self._captured_err = self.captureclass.EMPTY_BUFFER + return CaptureResult(captured_out, captured_err) + + def _suspend(self) -> None: + """Suspend this fixture's own capturing temporarily.""" + if self._capture is not None: + self._capture.suspend_capturing() + + def _resume(self) -> None: + """Resume this fixture's own capturing temporarily.""" + if self._capture is not None: + self._capture.resume_capturing() + + def _is_started(self) -> bool: + """Whether actively capturing -- not disabled or closed.""" + if self._capture is not None: + return self._capture.is_started() + return False + + @contextlib.contextmanager + def disabled(self) -> Generator[None]: + """Temporarily disable capturing while inside the ``with`` block.""" + capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( + "capturemanager" + ) + with capmanager.global_and_fixture_disabled(): + yield + + +# The fixtures. + + +@fixture +def capsys(request: SubRequest) -> Generator[CaptureFixture[str]]: + r"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. + + The captured output is made available via ``capsys.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_output(capsys): + print("hello") + captured = capsys.readouterr() + assert captured.out == "hello\n" + """ + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(SysCapture, request, _ispytest=True) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() + + +@fixture +def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]: + r"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. + + The captured output is made available via ``capsysbinary.readouterr()`` + method calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``bytes`` objects. + + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_output(capsysbinary): + print("hello") + captured = capsysbinary.readouterr() + assert captured.out == b"hello\n" + """ + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(SysCaptureBinary, request, _ispytest=True) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() + + +@fixture +def capfd(request: SubRequest) -> Generator[CaptureFixture[str]]: + r"""Enable text capturing of writes to file descriptors ``1`` and ``2``. + + The captured output is made available via ``capfd.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfd): + os.system('echo "hello"') + captured = capfd.readouterr() + assert captured.out == "hello\n" + """ + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(FDCapture, request, _ispytest=True) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() + + +@fixture +def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]: + r"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``. + + The captured output is made available via ``capfd.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``byte`` objects. + + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfdbinary): + os.system('echo "hello"') + captured = capfdbinary.readouterr() + assert captured.out == b"hello\n" + + """ + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(FDCaptureBinary, request, _ispytest=True) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() diff --git a/.venv/Lib/site-packages/_pytest/compat.py b/.venv/Lib/site-packages/_pytest/compat.py new file mode 100644 index 0000000..614848e --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/compat.py @@ -0,0 +1,351 @@ +# mypy: allow-untyped-defs +"""Python version compatibility code.""" + +from __future__ import annotations + +import dataclasses +import enum +import functools +import inspect +from inspect import Parameter +from inspect import signature +import os +from pathlib import Path +import sys +from typing import Any +from typing import Callable +from typing import Final +from typing import NoReturn + +import py + + +#: constant to prepare valuing pylib path replacements/lazy proxies later on +# intended for removal in pytest 8.0 or 9.0 + +# fmt: off +# intentional space to create a fake difference for the verification +LEGACY_PATH = py.path. local +# fmt: on + + +def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: + """Internal wrapper to prepare lazy proxies for legacy_path instances""" + return LEGACY_PATH(path) + + +# fmt: off +# Singleton type for NOTSET, as described in: +# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions +class NotSetType(enum.Enum): + token = 0 +NOTSET: Final = NotSetType.token +# fmt: on + + +def is_generator(func: object) -> bool: + genfunc = inspect.isgeneratorfunction(func) + return genfunc and not iscoroutinefunction(func) + + +def iscoroutinefunction(func: object) -> bool: + """Return True if func is a coroutine function (a function defined with async + def syntax, and doesn't contain yield), or a function decorated with + @asyncio.coroutine. + + Note: copied and modified from Python 3.5's builtin coroutines.py to avoid + importing asyncio directly, which in turns also initializes the "logging" + module as a side-effect (see issue #8). + """ + return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False) + + +def is_async_function(func: object) -> bool: + """Return True if the given function seems to be an async function or + an async generator.""" + return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) + + +def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str: + function = get_real_func(function) + fn = Path(inspect.getfile(function)) + lineno = function.__code__.co_firstlineno + if curdir is not None: + try: + relfn = fn.relative_to(curdir) + except ValueError: + pass + else: + return "%s:%d" % (relfn, lineno + 1) + return "%s:%d" % (fn, lineno + 1) + + +def num_mock_patch_args(function) -> int: + """Return number of arguments used up by mock arguments (if any).""" + patchings = getattr(function, "patchings", None) + if not patchings: + return 0 + + mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object()) + ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object()) + + return len( + [ + p + for p in patchings + if not p.attribute_name + and (p.new is mock_sentinel or p.new is ut_mock_sentinel) + ] + ) + + +def getfuncargnames( + function: Callable[..., object], + *, + name: str = "", + cls: type | None = None, +) -> tuple[str, ...]: + """Return the names of a function's mandatory arguments. + + Should return the names of all function arguments that: + * Aren't bound to an instance or type as in instance or class methods. + * Don't have default values. + * Aren't bound with functools.partial. + * Aren't replaced with mocks. + + The cls arguments indicate that the function should be treated as a bound + method even though it's not unless the function is a static method. + + The name parameter should be the original name in which the function was collected. + """ + # TODO(RonnyPfannschmidt): This function should be refactored when we + # revisit fixtures. The fixture mechanism should ask the node for + # the fixture names, and not try to obtain directly from the + # function object well after collection has occurred. + + # The parameters attribute of a Signature object contains an + # ordered mapping of parameter names to Parameter instances. This + # creates a tuple of the names of the parameters that don't have + # defaults. + try: + parameters = signature(function).parameters + except (ValueError, TypeError) as e: + from _pytest.outcomes import fail + + fail( + f"Could not determine arguments of {function!r}: {e}", + pytrace=False, + ) + + arg_names = tuple( + p.name + for p in parameters.values() + if ( + p.kind is Parameter.POSITIONAL_OR_KEYWORD + or p.kind is Parameter.KEYWORD_ONLY + ) + and p.default is Parameter.empty + ) + if not name: + name = function.__name__ + + # If this function should be treated as a bound method even though + # it's passed as an unbound method or function, remove the first + # parameter name. + if ( + # Not using `getattr` because we don't want to resolve the staticmethod. + # Not using `cls.__dict__` because we want to check the entire MRO. + cls + and not isinstance( + inspect.getattr_static(cls, name, default=None), staticmethod + ) + ): + arg_names = arg_names[1:] + # Remove any names that will be replaced with mocks. + if hasattr(function, "__wrapped__"): + arg_names = arg_names[num_mock_patch_args(function) :] + return arg_names + + +def get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]: + # Note: this code intentionally mirrors the code at the beginning of + # getfuncargnames, to get the arguments which were excluded from its result + # because they had default values. + return tuple( + p.name + for p in signature(function).parameters.values() + if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) + and p.default is not Parameter.empty + ) + + +_non_printable_ascii_translate_table = { + i: f"\\x{i:02x}" for i in range(128) if i not in range(32, 127) +} +_non_printable_ascii_translate_table.update( + {ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"} +) + + +def ascii_escaped(val: bytes | str) -> str: + r"""If val is pure ASCII, return it as an str, otherwise, escape + bytes objects into a sequence of escaped bytes: + + b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6' + + and escapes strings into a sequence of escaped unicode ids, e.g.: + + r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944' + + Note: + The obvious "v.decode('unicode-escape')" will return + valid UTF-8 unicode if it finds them in bytes, but we + want to return escaped bytes for any byte, even if they match + a UTF-8 string. + """ + if isinstance(val, bytes): + ret = val.decode("ascii", "backslashreplace") + else: + ret = val.encode("unicode_escape").decode("ascii") + return ret.translate(_non_printable_ascii_translate_table) + + +@dataclasses.dataclass +class _PytestWrapper: + """Dummy wrapper around a function object for internal use only. + + Used to correctly unwrap the underlying function object when we are + creating fixtures, because we wrap the function object ourselves with a + decorator to issue warnings when the fixture function is called directly. + """ + + obj: Any + + +def get_real_func(obj): + """Get the real function object of the (possibly) wrapped object by + functools.wraps or functools.partial.""" + start_obj = obj + for i in range(100): + # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function + # to trigger a warning if it gets called directly instead of by pytest: we don't + # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774) + new_obj = getattr(obj, "__pytest_wrapped__", None) + if isinstance(new_obj, _PytestWrapper): + obj = new_obj.obj + break + new_obj = getattr(obj, "__wrapped__", None) + if new_obj is None: + break + obj = new_obj + else: + from _pytest._io.saferepr import saferepr + + raise ValueError( + f"could not find real function of {saferepr(start_obj)}\nstopped at {saferepr(obj)}" + ) + if isinstance(obj, functools.partial): + obj = obj.func + return obj + + +def get_real_method(obj, holder): + """Attempt to obtain the real function object that might be wrapping + ``obj``, while at the same time returning a bound method to ``holder`` if + the original object was a bound method.""" + try: + is_method = hasattr(obj, "__func__") + obj = get_real_func(obj) + except Exception: # pragma: no cover + return obj + if is_method and hasattr(obj, "__get__") and callable(obj.__get__): + obj = obj.__get__(holder) + return obj + + +def getimfunc(func): + try: + return func.__func__ + except AttributeError: + return func + + +def safe_getattr(object: Any, name: str, default: Any) -> Any: + """Like getattr but return default upon any Exception or any OutcomeException. + + Attribute access can potentially fail for 'evil' Python objects. + See issue #214. + It catches OutcomeException because of #2490 (issue #580), new outcomes + are derived from BaseException instead of Exception (for more details + check #2707). + """ + from _pytest.outcomes import TEST_OUTCOME + + try: + return getattr(object, name, default) + except TEST_OUTCOME: + return default + + +def safe_isclass(obj: object) -> bool: + """Ignore any exception via isinstance on Python 3.""" + try: + return inspect.isclass(obj) + except Exception: + return False + + +def get_user_id() -> int | None: + """Return the current process's real user id or None if it could not be + determined. + + :return: The user id or None if it could not be determined. + """ + # mypy follows the version and platform checking expectation of PEP 484: + # https://mypy.readthedocs.io/en/stable/common_issues.html?highlight=platform#python-version-and-system-platform-checks + # Containment checks are too complex for mypy v1.5.0 and cause failure. + if sys.platform == "win32" or sys.platform == "emscripten": + # win32 does not have a getuid() function. + # Emscripten has a return 0 stub. + return None + else: + # On other platforms, a return value of -1 is assumed to indicate that + # the current process's real user id could not be determined. + ERROR = -1 + uid = os.getuid() + return uid if uid != ERROR else None + + +# Perform exhaustiveness checking. +# +# Consider this example: +# +# MyUnion = Union[int, str] +# +# def handle(x: MyUnion) -> int { +# if isinstance(x, int): +# return 1 +# elif isinstance(x, str): +# return 2 +# else: +# raise Exception('unreachable') +# +# Now suppose we add a new variant: +# +# MyUnion = Union[int, str, bytes] +# +# After doing this, we must remember ourselves to go and update the handle +# function to handle the new variant. +# +# With `assert_never` we can do better: +# +# // raise Exception('unreachable') +# return assert_never(x) +# +# Now, if we forget to handle the new variant, the type-checker will emit a +# compile-time error, instead of the runtime error we would have gotten +# previously. +# +# This also work for Enums (if you use `is` to compare) and Literals. +def assert_never(value: NoReturn) -> NoReturn: + assert False, f"Unhandled value: {value} ({type(value).__name__})" diff --git a/.venv/Lib/site-packages/_pytest/config/__init__.py b/.venv/Lib/site-packages/_pytest/config/__init__.py new file mode 100644 index 0000000..710e03e --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/config/__init__.py @@ -0,0 +1,1973 @@ +# mypy: allow-untyped-defs +"""Command line options, ini-file and conftest.py processing.""" + +from __future__ import annotations + +import argparse +import collections.abc +import copy +import dataclasses +import enum +from functools import lru_cache +import glob +import importlib.metadata +import inspect +import os +import pathlib +import re +import shlex +import sys +from textwrap import dedent +import types +from types import FunctionType +from typing import Any +from typing import Callable +from typing import cast +from typing import Final +from typing import final +from typing import Generator +from typing import IO +from typing import Iterable +from typing import Iterator +from typing import Sequence +from typing import TextIO +from typing import Type +from typing import TYPE_CHECKING +import warnings + +import pluggy +from pluggy import HookimplMarker +from pluggy import HookimplOpts +from pluggy import HookspecMarker +from pluggy import HookspecOpts +from pluggy import PluginManager + +from .compat import PathAwareHookProxy +from .exceptions import PrintHelp as PrintHelp +from .exceptions import UsageError as UsageError +from .findpaths import determine_setup +from _pytest import __version__ +import _pytest._code +from _pytest._code import ExceptionInfo +from _pytest._code import filter_traceback +from _pytest._code.code import TracebackStyle +from _pytest._io import TerminalWriter +from _pytest.config.argparsing import Argument +from _pytest.config.argparsing import Parser +import _pytest.deprecated +import _pytest.hookspec +from _pytest.outcomes import fail +from _pytest.outcomes import Skipped +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import import_path +from _pytest.pathlib import ImportMode +from _pytest.pathlib import resolve_package_path +from _pytest.pathlib import safe_exists +from _pytest.stash import Stash +from _pytest.warning_types import PytestConfigWarning +from _pytest.warning_types import warn_explicit_for + + +if TYPE_CHECKING: + from _pytest.cacheprovider import Cache + from _pytest.terminal import TerminalReporter + + +_PluggyPlugin = object +"""A type to represent plugin objects. + +Plugins can be any namespace, so we can't narrow it down much, but we use an +alias to make the intent clear. + +Ideally this type would be provided by pluggy itself. +""" + + +hookimpl = HookimplMarker("pytest") +hookspec = HookspecMarker("pytest") + + +@final +class ExitCode(enum.IntEnum): + """Encodes the valid exit codes by pytest. + + Currently users and plugins may supply other exit codes as well. + + .. versionadded:: 5.0 + """ + + #: Tests passed. + OK = 0 + #: Tests failed. + TESTS_FAILED = 1 + #: pytest was interrupted. + INTERRUPTED = 2 + #: An internal error got in the way. + INTERNAL_ERROR = 3 + #: pytest was misused. + USAGE_ERROR = 4 + #: pytest couldn't find tests. + NO_TESTS_COLLECTED = 5 + + +class ConftestImportFailure(Exception): + def __init__( + self, + path: pathlib.Path, + *, + cause: Exception, + ) -> None: + self.path = path + self.cause = cause + + def __str__(self) -> str: + return f"{type(self.cause).__name__}: {self.cause} (from {self.path})" + + +def filter_traceback_for_conftest_import_failure( + entry: _pytest._code.TracebackEntry, +) -> bool: + """Filter tracebacks entries which point to pytest internals or importlib. + + Make a special case for importlib because we use it to import test modules and conftest files + in _pytest.pathlib.import_path. + """ + return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep) + + +def main( + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> int | ExitCode: + """Perform an in-process test run. + + :param args: + List of command line arguments. If `None` or not given, defaults to reading + arguments directly from the process command line (:data:`sys.argv`). + :param plugins: List of plugin objects to be auto-registered during initialization. + + :returns: An exit code. + """ + old_pytest_version = os.environ.get("PYTEST_VERSION") + try: + os.environ["PYTEST_VERSION"] = __version__ + try: + config = _prepareconfig(args, plugins) + except ConftestImportFailure as e: + exc_info = ExceptionInfo.from_exception(e.cause) + tw = TerminalWriter(sys.stderr) + tw.line(f"ImportError while loading conftest '{e.path}'.", red=True) + exc_info.traceback = exc_info.traceback.filter( + filter_traceback_for_conftest_import_failure + ) + exc_repr = ( + exc_info.getrepr(style="short", chain=False) + if exc_info.traceback + else exc_info.exconly() + ) + formatted_tb = str(exc_repr) + for line in formatted_tb.splitlines(): + tw.line(line.rstrip(), red=True) + return ExitCode.USAGE_ERROR + else: + try: + ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) + try: + return ExitCode(ret) + except ValueError: + return ret + finally: + config._ensure_unconfigure() + except UsageError as e: + tw = TerminalWriter(sys.stderr) + for msg in e.args: + tw.line(f"ERROR: {msg}\n", red=True) + return ExitCode.USAGE_ERROR + finally: + if old_pytest_version is None: + os.environ.pop("PYTEST_VERSION", None) + else: + os.environ["PYTEST_VERSION"] = old_pytest_version + + +def console_main() -> int: + """The CLI entry point of pytest. + + This function is not meant for programmable use; use `main()` instead. + """ + # https://docs.python.org/3/library/signal.html#note-on-sigpipe + try: + code = main() + sys.stdout.flush() + return code + except BrokenPipeError: + # Python flushes standard streams on exit; redirect remaining output + # to devnull to avoid another BrokenPipeError at shutdown + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + return 1 # Python exits with error code 1 on EPIPE + + +class cmdline: # compatibility namespace + main = staticmethod(main) + + +def filename_arg(path: str, optname: str) -> str: + """Argparse type validator for filename arguments. + + :path: Path of filename. + :optname: Name of the option. + """ + if os.path.isdir(path): + raise UsageError(f"{optname} must be a filename, given: {path}") + return path + + +def directory_arg(path: str, optname: str) -> str: + """Argparse type validator for directory arguments. + + :path: Path of directory. + :optname: Name of the option. + """ + if not os.path.isdir(path): + raise UsageError(f"{optname} must be a directory, given: {path}") + return path + + +# Plugins that cannot be disabled via "-p no:X" currently. +essential_plugins = ( + "mark", + "main", + "runner", + "fixtures", + "helpconfig", # Provides -p. +) + +default_plugins = ( + *essential_plugins, + "python", + "terminal", + "debugging", + "unittest", + "capture", + "skipping", + "legacypath", + "tmpdir", + "monkeypatch", + "recwarn", + "pastebin", + "assertion", + "junitxml", + "doctest", + "cacheprovider", + "freeze_support", + "setuponly", + "setupplan", + "stepwise", + "warnings", + "logging", + "reports", + "python_path", + "unraisableexception", + "threadexception", + "faulthandler", +) + +builtin_plugins = set(default_plugins) +builtin_plugins.add("pytester") +builtin_plugins.add("pytester_assertions") + + +def get_config( + args: list[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: + # subsequent calls to main will create a fresh instance + pluginmanager = PytestPluginManager() + config = Config( + pluginmanager, + invocation_params=Config.InvocationParams( + args=args or (), + plugins=plugins, + dir=pathlib.Path.cwd(), + ), + ) + + if args is not None: + # Handle any "-p no:plugin" args. + pluginmanager.consider_preparse(args, exclude_only=True) + + for spec in default_plugins: + pluginmanager.import_plugin(spec) + + return config + + +def get_plugin_manager() -> PytestPluginManager: + """Obtain a new instance of the + :py:class:`pytest.PytestPluginManager`, with default plugins + already loaded. + + This function can be used by integration with other tools, like hooking + into pytest to run tests into an IDE. + """ + return get_config().pluginmanager + + +def _prepareconfig( + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: + if args is None: + args = sys.argv[1:] + elif isinstance(args, os.PathLike): + args = [os.fspath(args)] + elif not isinstance(args, list): + msg = ( # type:ignore[unreachable] + "`args` parameter expected to be a list of strings, got: {!r} (type: {})" + ) + raise TypeError(msg.format(args, type(args))) + + config = get_config(args, plugins) + pluginmanager = config.pluginmanager + try: + if plugins: + for plugin in plugins: + if isinstance(plugin, str): + pluginmanager.consider_pluginarg(plugin) + else: + pluginmanager.register(plugin) + config = pluginmanager.hook.pytest_cmdline_parse( + pluginmanager=pluginmanager, args=args + ) + return config + except BaseException: + config._ensure_unconfigure() + raise + + +def _get_directory(path: pathlib.Path) -> pathlib.Path: + """Get the directory of a path - itself if already a directory.""" + if path.is_file(): + return path.parent + else: + return path + + +def _get_legacy_hook_marks( + method: Any, + hook_type: str, + opt_names: tuple[str, ...], +) -> dict[str, bool]: + if TYPE_CHECKING: + # abuse typeguard from importlib to avoid massive method type union that's lacking an alias + assert inspect.isroutine(method) + known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} + must_warn: list[str] = [] + opts: dict[str, bool] = {} + for opt_name in opt_names: + opt_attr = getattr(method, opt_name, AttributeError) + if opt_attr is not AttributeError: + must_warn.append(f"{opt_name}={opt_attr}") + opts[opt_name] = True + elif opt_name in known_marks: + must_warn.append(f"{opt_name}=True") + opts[opt_name] = True + else: + opts[opt_name] = False + if must_warn: + hook_opts = ", ".join(must_warn) + message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( + type=hook_type, + fullname=method.__qualname__, + hook_opts=hook_opts, + ) + warn_explicit_for(cast(FunctionType, method), message) + return opts + + +@final +class PytestPluginManager(PluginManager): + """A :py:class:`pluggy.PluginManager ` with + additional pytest-specific functionality: + + * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and + ``pytest_plugins`` global variables found in plugins being loaded. + * ``conftest.py`` loading during start-up. + """ + + def __init__(self) -> None: + import _pytest.assertion + + super().__init__("pytest") + + # -- State related to local conftest plugins. + # All loaded conftest modules. + self._conftest_plugins: set[types.ModuleType] = set() + # All conftest modules applicable for a directory. + # This includes the directory's own conftest modules as well + # as those of its parent directories. + self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {} + # Cutoff directory above which conftests are no longer discovered. + self._confcutdir: pathlib.Path | None = None + # If set, conftest loading is skipped. + self._noconftest = False + + # _getconftestmodules()'s call to _get_directory() causes a stat + # storm when it's called potentially thousands of times in a test + # session (#9478), often with the same path, so cache it. + self._get_directory = lru_cache(256)(_get_directory) + + # plugins that were explicitly skipped with pytest.skip + # list of (module name, skip reason) + # previously we would issue a warning when a plugin was skipped, but + # since we refactored warnings as first citizens of Config, they are + # just stored here to be used later. + self.skipped_plugins: list[tuple[str, str]] = [] + + self.add_hookspecs(_pytest.hookspec) + self.register(self) + if os.environ.get("PYTEST_DEBUG"): + err: IO[str] = sys.stderr + encoding: str = getattr(err, "encoding", "utf8") + try: + err = open( + os.dup(err.fileno()), + mode=err.mode, + buffering=1, + encoding=encoding, + ) + except Exception: + pass + self.trace.root.setwriter(err.write) + self.enable_tracing() + + # Config._consider_importhook will set a real object if required. + self.rewrite_hook = _pytest.assertion.DummyRewriteHook() + # Used to know when we are importing conftests after the pytest_configure stage. + self._configured = False + + def parse_hookimpl_opts( + self, plugin: _PluggyPlugin, name: str + ) -> HookimplOpts | None: + """:meta private:""" + # pytest hooks are always prefixed with "pytest_", + # so we avoid accessing possibly non-readable attributes + # (see issue #1073). + if not name.startswith("pytest_"): + return None + # Ignore names which cannot be hooks. + if name == "pytest_plugins": + return None + + opts = super().parse_hookimpl_opts(plugin, name) + if opts is not None: + return opts + + method = getattr(plugin, name) + # Consider only actual functions for hooks (#3775). + if not inspect.isroutine(method): + return None + # Collect unmarked hooks as long as they have the `pytest_' prefix. + return _get_legacy_hook_marks( # type: ignore[return-value] + method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") + ) + + def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None: + """:meta private:""" + opts = super().parse_hookspec_opts(module_or_class, name) + if opts is None: + method = getattr(module_or_class, name) + if name.startswith("pytest_"): + opts = _get_legacy_hook_marks( # type: ignore[assignment] + method, + "spec", + ("firstresult", "historic"), + ) + return opts + + def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: + if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: + warnings.warn( + PytestConfigWarning( + "{} plugin has been merged into the core, " + "please remove it from your requirements.".format( + name.replace("_", "-") + ) + ) + ) + return None + plugin_name = super().register(plugin, name) + if plugin_name is not None: + self.hook.pytest_plugin_registered.call_historic( + kwargs=dict( + plugin=plugin, + plugin_name=plugin_name, + manager=self, + ) + ) + + if isinstance(plugin, types.ModuleType): + self.consider_module(plugin) + return plugin_name + + def getplugin(self, name: str): + # Support deprecated naming because plugins (xdist e.g.) use it. + plugin: _PluggyPlugin | None = self.get_plugin(name) + return plugin + + def hasplugin(self, name: str) -> bool: + """Return whether a plugin with the given name is registered.""" + return bool(self.get_plugin(name)) + + def pytest_configure(self, config: Config) -> None: + """:meta private:""" + # XXX now that the pluginmanager exposes hookimpl(tryfirst...) + # we should remove tryfirst/trylast as markers. + config.addinivalue_line( + "markers", + "tryfirst: mark a hook implementation function such that the " + "plugin machinery will try to call it first/as early as possible. " + "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.", + ) + config.addinivalue_line( + "markers", + "trylast: mark a hook implementation function such that the " + "plugin machinery will try to call it last/as late as possible. " + "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.", + ) + self._configured = True + + # + # Internal API for local conftest plugin handling. + # + def _set_initial_conftests( + self, + args: Sequence[str | pathlib.Path], + pyargs: bool, + noconftest: bool, + rootpath: pathlib.Path, + confcutdir: pathlib.Path | None, + invocation_dir: pathlib.Path, + importmode: ImportMode | str, + *, + consider_namespace_packages: bool, + ) -> None: + """Load initial conftest files given a preparsed "namespace". + + As conftest files may add their own command line options which have + arguments ('--my-opt somepath') we might get some false positives. + All builtin and 3rd party plugins will have been loaded, however, so + common options will not confuse our logic here. + """ + self._confcutdir = ( + absolutepath(invocation_dir / confcutdir) if confcutdir else None + ) + self._noconftest = noconftest + self._using_pyargs = pyargs + foundanchor = False + for initial_path in args: + path = str(initial_path) + # remove node-id syntax + i = path.find("::") + if i != -1: + path = path[:i] + anchor = absolutepath(invocation_dir / path) + + # Ensure we do not break if what appears to be an anchor + # is in fact a very long option (#10169, #11394). + if safe_exists(anchor): + self._try_load_conftest( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + foundanchor = True + if not foundanchor: + self._try_load_conftest( + invocation_dir, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + + def _is_in_confcutdir(self, path: pathlib.Path) -> bool: + """Whether to consider the given path to load conftests from.""" + if self._confcutdir is None: + return True + # The semantics here are literally: + # Do not load a conftest if it is found upwards from confcut dir. + # But this is *not* the same as: + # Load only conftests from confcutdir or below. + # At first glance they might seem the same thing, however we do support use cases where + # we want to load conftests that are not found in confcutdir or below, but are found + # in completely different directory hierarchies like packages installed + # in out-of-source trees. + # (see #9767 for a regression where the logic was inverted). + return path not in self._confcutdir.parents + + def _try_load_conftest( + self, + anchor: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, + ) -> None: + self._loadconftestmodules( + anchor, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + # let's also consider test* subdirs + if anchor.is_dir(): + for x in anchor.glob("test*"): + if x.is_dir(): + self._loadconftestmodules( + x, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + + def _loadconftestmodules( + self, + path: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, + ) -> None: + if self._noconftest: + return + + directory = self._get_directory(path) + + # Optimization: avoid repeated searches in the same directory. + # Assumes always called with same importmode and rootpath. + if directory in self._dirpath2confmods: + return + + clist = [] + for parent in reversed((directory, *directory.parents)): + if self._is_in_confcutdir(parent): + conftestpath = parent / "conftest.py" + if conftestpath.is_file(): + mod = self._importconftest( + conftestpath, + importmode, + rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + clist.append(mod) + self._dirpath2confmods[directory] = clist + + def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]: + directory = self._get_directory(path) + return self._dirpath2confmods.get(directory, ()) + + def _rget_with_confmod( + self, + name: str, + path: pathlib.Path, + ) -> tuple[types.ModuleType, Any]: + modules = self._getconftestmodules(path) + for mod in reversed(modules): + try: + return mod, getattr(mod, name) + except AttributeError: + continue + raise KeyError(name) + + def _importconftest( + self, + conftestpath: pathlib.Path, + importmode: str | ImportMode, + rootpath: pathlib.Path, + *, + consider_namespace_packages: bool, + ) -> types.ModuleType: + conftestpath_plugin_name = str(conftestpath) + existing = self.get_plugin(conftestpath_plugin_name) + if existing is not None: + return cast(types.ModuleType, existing) + + # conftest.py files there are not in a Python package all have module + # name "conftest", and thus conflict with each other. Clear the existing + # before loading the new one, otherwise the existing one will be + # returned from the module cache. + pkgpath = resolve_package_path(conftestpath) + if pkgpath is None: + try: + del sys.modules[conftestpath.stem] + except KeyError: + pass + + try: + mod = import_path( + conftestpath, + mode=importmode, + root=rootpath, + consider_namespace_packages=consider_namespace_packages, + ) + except Exception as e: + assert e.__traceback__ is not None + raise ConftestImportFailure(conftestpath, cause=e) from e + + self._check_non_top_pytest_plugins(mod, conftestpath) + + self._conftest_plugins.add(mod) + dirpath = conftestpath.parent + if dirpath in self._dirpath2confmods: + for path, mods in self._dirpath2confmods.items(): + if dirpath in path.parents or path == dirpath: + if mod in mods: + raise AssertionError( + f"While trying to load conftest path {conftestpath!s}, " + f"found that the module {mod} is already loaded with path {mod.__file__}. " + "This is not supposed to happen. Please report this issue to pytest." + ) + mods.append(mod) + self.trace(f"loading conftestmodule {mod!r}") + self.consider_conftest(mod, registration_name=conftestpath_plugin_name) + return mod + + def _check_non_top_pytest_plugins( + self, + mod: types.ModuleType, + conftestpath: pathlib.Path, + ) -> None: + if ( + hasattr(mod, "pytest_plugins") + and self._configured + and not self._using_pyargs + ): + msg = ( + "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n" + "It affects the entire test suite instead of just below the conftest as expected.\n" + " {}\n" + "Please move it to a top level conftest file at the rootdir:\n" + " {}\n" + "For more information, visit:\n" + " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files" + ) + fail(msg.format(conftestpath, self._confcutdir), pytrace=False) + + # + # API for bootstrapping plugin loading + # + # + + def consider_preparse( + self, args: Sequence[str], *, exclude_only: bool = False + ) -> None: + """:meta private:""" + i = 0 + n = len(args) + while i < n: + opt = args[i] + i += 1 + if isinstance(opt, str): + if opt == "-p": + try: + parg = args[i] + except IndexError: + return + i += 1 + elif opt.startswith("-p"): + parg = opt[2:] + else: + continue + parg = parg.strip() + if exclude_only and not parg.startswith("no:"): + continue + self.consider_pluginarg(parg) + + def consider_pluginarg(self, arg: str) -> None: + """:meta private:""" + if arg.startswith("no:"): + name = arg[3:] + if name in essential_plugins: + raise UsageError(f"plugin {name} cannot be disabled") + + # PR #4304: remove stepwise if cacheprovider is blocked. + if name == "cacheprovider": + self.set_blocked("stepwise") + self.set_blocked("pytest_stepwise") + + self.set_blocked(name) + if not name.startswith("pytest_"): + self.set_blocked("pytest_" + name) + else: + name = arg + # Unblock the plugin. + self.unblock(name) + if not name.startswith("pytest_"): + self.unblock("pytest_" + name) + self.import_plugin(arg, consider_entry_points=True) + + def consider_conftest( + self, conftestmodule: types.ModuleType, registration_name: str + ) -> None: + """:meta private:""" + self.register(conftestmodule, name=registration_name) + + def consider_env(self) -> None: + """:meta private:""" + self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) + + def consider_module(self, mod: types.ModuleType) -> None: + """:meta private:""" + self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) + + def _import_plugin_specs( + self, spec: None | types.ModuleType | str | Sequence[str] + ) -> None: + plugins = _get_plugin_specs_as_list(spec) + for import_spec in plugins: + self.import_plugin(import_spec) + + def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None: + """Import a plugin with ``modname``. + + If ``consider_entry_points`` is True, entry point names are also + considered to find a plugin. + """ + # Most often modname refers to builtin modules, e.g. "pytester", + # "terminal" or "capture". Those plugins are registered under their + # basename for historic purposes but must be imported with the + # _pytest prefix. + assert isinstance( + modname, str + ), f"module name as text required, got {modname!r}" + if self.is_blocked(modname) or self.get_plugin(modname) is not None: + return + + importspec = "_pytest." + modname if modname in builtin_plugins else modname + self.rewrite_hook.mark_rewrite(importspec) + + if consider_entry_points: + loaded = self.load_setuptools_entrypoints("pytest11", name=modname) + if loaded: + return + + try: + __import__(importspec) + except ImportError as e: + raise ImportError( + f'Error importing plugin "{modname}": {e.args[0]}' + ).with_traceback(e.__traceback__) from e + + except Skipped as e: + self.skipped_plugins.append((modname, e.msg or "")) + else: + mod = sys.modules[importspec] + self.register(mod, modname) + + +def _get_plugin_specs_as_list( + specs: None | types.ModuleType | str | Sequence[str], +) -> list[str]: + """Parse a plugins specification into a list of plugin names.""" + # None means empty. + if specs is None: + return [] + # Workaround for #3899 - a submodule which happens to be called "pytest_plugins". + if isinstance(specs, types.ModuleType): + return [] + # Comma-separated list. + if isinstance(specs, str): + return specs.split(",") if specs else [] + # Direct specification. + if isinstance(specs, collections.abc.Sequence): + return list(specs) + raise UsageError( + f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}" + ) + + +class Notset: + def __repr__(self): + return "" + + +notset = Notset() + + +def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: + """Given an iterable of file names in a source distribution, return the "names" that should + be marked for assertion rewrite. + + For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in + the assertion rewrite mechanism. + + This function has to deal with dist-info based distributions and egg based distributions + (which are still very much in use for "editable" installs). + + Here are the file names as seen in a dist-info based distribution: + + pytest_mock/__init__.py + pytest_mock/_version.py + pytest_mock/plugin.py + pytest_mock.egg-info/PKG-INFO + + Here are the file names as seen in an egg based distribution: + + src/pytest_mock/__init__.py + src/pytest_mock/_version.py + src/pytest_mock/plugin.py + src/pytest_mock.egg-info/PKG-INFO + LICENSE + setup.py + + We have to take in account those two distribution flavors in order to determine which + names should be considered for assertion rewriting. + + More information: + https://github.com/pytest-dev/pytest-mock/issues/167 + """ + package_files = list(package_files) + seen_some = False + for fn in package_files: + is_simple_module = "/" not in fn and fn.endswith(".py") + is_package = fn.count("/") == 1 and fn.endswith("__init__.py") + if is_simple_module: + module_name, _ = os.path.splitext(fn) + # we ignore "setup.py" at the root of the distribution + # as well as editable installation finder modules made by setuptools + if module_name != "setup" and not module_name.startswith("__editable__"): + seen_some = True + yield module_name + elif is_package: + package_name = os.path.dirname(fn) + seen_some = True + yield package_name + + if not seen_some: + # At this point we did not find any packages or modules suitable for assertion + # rewriting, so we try again by stripping the first path component (to account for + # "src" based source trees for example). + # This approach lets us have the common case continue to be fast, as egg-distributions + # are rarer. + new_package_files = [] + for fn in package_files: + parts = fn.split("/") + new_fn = "/".join(parts[1:]) + if new_fn: + new_package_files.append(new_fn) + if new_package_files: + yield from _iter_rewritable_modules(new_package_files) + + +@final +class Config: + """Access to configuration values, pluginmanager and plugin hooks. + + :param PytestPluginManager pluginmanager: + A pytest PluginManager. + + :param InvocationParams invocation_params: + Object containing parameters regarding the :func:`pytest.main` + invocation. + """ + + @final + @dataclasses.dataclass(frozen=True) + class InvocationParams: + """Holds parameters passed during :func:`pytest.main`. + + The object attributes are read-only. + + .. versionadded:: 5.1 + + .. note:: + + Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` + ini option are handled by pytest, not being included in the ``args`` attribute. + + Plugins accessing ``InvocationParams`` must be aware of that. + """ + + args: tuple[str, ...] + """The command-line arguments as passed to :func:`pytest.main`.""" + plugins: Sequence[str | _PluggyPlugin] | None + """Extra plugins, might be `None`.""" + dir: pathlib.Path + """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path""" + + def __init__( + self, + *, + args: Iterable[str], + plugins: Sequence[str | _PluggyPlugin] | None, + dir: pathlib.Path, + ) -> None: + object.__setattr__(self, "args", tuple(args)) + object.__setattr__(self, "plugins", plugins) + object.__setattr__(self, "dir", dir) + + class ArgsSource(enum.Enum): + """Indicates the source of the test arguments. + + .. versionadded:: 7.2 + """ + + #: Command line arguments. + ARGS = enum.auto() + #: Invocation directory. + INVOCATION_DIR = enum.auto() + INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias + #: 'testpaths' configuration value. + TESTPATHS = enum.auto() + + # Set by cacheprovider plugin. + cache: Cache + + def __init__( + self, + pluginmanager: PytestPluginManager, + *, + invocation_params: InvocationParams | None = None, + ) -> None: + from .argparsing import FILE_OR_DIR + from .argparsing import Parser + + if invocation_params is None: + invocation_params = self.InvocationParams( + args=(), plugins=None, dir=pathlib.Path.cwd() + ) + + self.option = argparse.Namespace() + """Access to command line option as attributes. + + :type: argparse.Namespace + """ + + self.invocation_params = invocation_params + """The parameters with which pytest was invoked. + + :type: InvocationParams + """ + + _a = FILE_OR_DIR + self._parser = Parser( + usage=f"%(prog)s [options] [{_a}] [{_a}] [...]", + processopt=self._processopt, + _ispytest=True, + ) + self.pluginmanager = pluginmanager + """The plugin manager handles plugin registration and hook invocation. + + :type: PytestPluginManager + """ + + self.stash = Stash() + """A place where plugins can store information on the config for their + own use. + + :type: Stash + """ + # Deprecated alias. Was never public. Can be removed in a few releases. + self._store = self.stash + + self.trace = self.pluginmanager.trace.root.get("config") + self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] + self._inicache: dict[str, Any] = {} + self._override_ini: Sequence[str] = () + self._opt2dest: dict[str, str] = {} + self._cleanup: list[Callable[[], None]] = [] + self.pluginmanager.register(self, "pytestconfig") + self._configured = False + self.hook.pytest_addoption.call_historic( + kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) + ) + self.args_source = Config.ArgsSource.ARGS + self.args: list[str] = [] + + @property + def rootpath(self) -> pathlib.Path: + """The path to the :ref:`rootdir `. + + :type: pathlib.Path + + .. versionadded:: 6.1 + """ + return self._rootpath + + @property + def inipath(self) -> pathlib.Path | None: + """The path to the :ref:`configfile `. + + .. versionadded:: 6.1 + """ + return self._inipath + + def add_cleanup(self, func: Callable[[], None]) -> None: + """Add a function to be called when the config object gets out of + use (usually coinciding with pytest_unconfigure).""" + self._cleanup.append(func) + + def _do_configure(self) -> None: + assert not self._configured + self._configured = True + with warnings.catch_warnings(): + warnings.simplefilter("default") + self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) + + def _ensure_unconfigure(self) -> None: + if self._configured: + self._configured = False + self.hook.pytest_unconfigure(config=self) + self.hook.pytest_configure._call_history = [] + while self._cleanup: + fin = self._cleanup.pop() + fin() + + def get_terminal_writer(self) -> TerminalWriter: + terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin( + "terminalreporter" + ) + assert terminalreporter is not None + return terminalreporter._tw + + def pytest_cmdline_parse( + self, pluginmanager: PytestPluginManager, args: list[str] + ) -> Config: + try: + self.parse(args) + except UsageError: + # Handle --version and --help here in a minimal fashion. + # This gets done via helpconfig normally, but its + # pytest_cmdline_main is not called in case of errors. + if getattr(self.option, "version", False) or "--version" in args: + from _pytest.helpconfig import showversion + + showversion(self) + elif ( + getattr(self.option, "help", False) or "--help" in args or "-h" in args + ): + self._parser._getparser().print_help() + sys.stdout.write( + "\nNOTE: displaying only minimal help due to UsageError.\n\n" + ) + + raise + + return self + + def notify_exception( + self, + excinfo: ExceptionInfo[BaseException], + option: argparse.Namespace | None = None, + ) -> None: + if option and getattr(option, "fulltrace", False): + style: TracebackStyle = "long" + else: + style = "native" + excrepr = excinfo.getrepr( + funcargs=True, showlocals=getattr(option, "showlocals", False), style=style + ) + res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) + if not any(res): + for line in str(excrepr).split("\n"): + sys.stderr.write(f"INTERNALERROR> {line}\n") + sys.stderr.flush() + + def cwd_relative_nodeid(self, nodeid: str) -> str: + # nodeid's are relative to the rootpath, compute relative to cwd. + if self.invocation_params.dir != self.rootpath: + base_path_part, *nodeid_part = nodeid.split("::") + # Only process path part + fullpath = self.rootpath / base_path_part + relative_path = bestrelpath(self.invocation_params.dir, fullpath) + + nodeid = "::".join([relative_path, *nodeid_part]) + return nodeid + + @classmethod + def fromdictargs(cls, option_dict, args) -> Config: + """Constructor usable for subprocesses.""" + config = get_config(args) + config.option.__dict__.update(option_dict) + config.parse(args, addopts=False) + for x in config.option.plugins: + config.pluginmanager.consider_pluginarg(x) + return config + + def _processopt(self, opt: Argument) -> None: + for name in opt._short_opts + opt._long_opts: + self._opt2dest[name] = opt.dest + + if hasattr(opt, "default"): + if not hasattr(self.option, opt.dest): + setattr(self.option, opt.dest, opt.default) + + @hookimpl(trylast=True) + def pytest_load_initial_conftests(self, early_config: Config) -> None: + # We haven't fully parsed the command line arguments yet, so + # early_config.args it not set yet. But we need it for + # discovering the initial conftests. So "pre-run" the logic here. + # It will be done for real in `parse()`. + args, args_source = early_config._decide_args( + args=early_config.known_args_namespace.file_or_dir, + pyargs=early_config.known_args_namespace.pyargs, + testpaths=early_config.getini("testpaths"), + invocation_dir=early_config.invocation_params.dir, + rootpath=early_config.rootpath, + warn=False, + ) + self.pluginmanager._set_initial_conftests( + args=args, + pyargs=early_config.known_args_namespace.pyargs, + noconftest=early_config.known_args_namespace.noconftest, + rootpath=early_config.rootpath, + confcutdir=early_config.known_args_namespace.confcutdir, + invocation_dir=early_config.invocation_params.dir, + importmode=early_config.known_args_namespace.importmode, + consider_namespace_packages=early_config.getini( + "consider_namespace_packages" + ), + ) + + def _initini(self, args: Sequence[str]) -> None: + ns, unknown_args = self._parser.parse_known_and_unknown_args( + args, namespace=copy.copy(self.option) + ) + rootpath, inipath, inicfg = determine_setup( + inifile=ns.inifilename, + args=ns.file_or_dir + unknown_args, + rootdir_cmd_arg=ns.rootdir or None, + invocation_dir=self.invocation_params.dir, + ) + self._rootpath = rootpath + self._inipath = inipath + self.inicfg = inicfg + self._parser.extra_info["rootdir"] = str(self.rootpath) + self._parser.extra_info["inifile"] = str(self.inipath) + self._parser.addini("addopts", "Extra command line options", "args") + self._parser.addini("minversion", "Minimally required pytest version") + self._parser.addini( + "required_plugins", + "Plugins that must be present for pytest to run", + type="args", + default=[], + ) + self._override_ini = ns.override_ini or () + + def _consider_importhook(self, args: Sequence[str]) -> None: + """Install the PEP 302 import hook if using assertion rewriting. + + Needs to parse the --assert= option from the commandline + and find all the installed plugins to mark them for rewriting + by the importhook. + """ + ns, unknown_args = self._parser.parse_known_and_unknown_args(args) + mode = getattr(ns, "assertmode", "plain") + if mode == "rewrite": + import _pytest.assertion + + try: + hook = _pytest.assertion.install_importhook(self) + except SystemError: + mode = "plain" + else: + self._mark_plugins_for_rewrite(hook) + self._warn_about_missing_assertion(mode) + + def _mark_plugins_for_rewrite(self, hook) -> None: + """Given an importhook, mark for rewrite any top-level + modules or packages in the distribution package for + all pytest plugins.""" + self.pluginmanager.rewrite_hook = hook + + if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): + # We don't autoload from distribution package entry points, + # no need to continue. + return + + package_files = ( + str(file) + for dist in importlib.metadata.distributions() + if any(ep.group == "pytest11" for ep in dist.entry_points) + for file in dist.files or [] + ) + + for name in _iter_rewritable_modules(package_files): + hook.mark_rewrite(name) + + def _validate_args(self, args: list[str], via: str) -> list[str]: + """Validate known args.""" + self._parser._config_source_hint = via # type: ignore + try: + self._parser.parse_known_and_unknown_args( + args, namespace=copy.copy(self.option) + ) + finally: + del self._parser._config_source_hint # type: ignore + + return args + + def _decide_args( + self, + *, + args: list[str], + pyargs: bool, + testpaths: list[str], + invocation_dir: pathlib.Path, + rootpath: pathlib.Path, + warn: bool, + ) -> tuple[list[str], ArgsSource]: + """Decide the args (initial paths/nodeids) to use given the relevant inputs. + + :param warn: Whether can issue warnings. + + :returns: The args and the args source. Guaranteed to be non-empty. + """ + if args: + source = Config.ArgsSource.ARGS + result = args + else: + if invocation_dir == rootpath: + source = Config.ArgsSource.TESTPATHS + if pyargs: + result = testpaths + else: + result = [] + for path in testpaths: + result.extend(sorted(glob.iglob(path, recursive=True))) + if testpaths and not result: + if warn: + warning_text = ( + "No files were found in testpaths; " + "consider removing or adjusting your testpaths configuration. " + "Searching recursively from the current directory instead." + ) + self.issue_config_time_warning( + PytestConfigWarning(warning_text), stacklevel=3 + ) + else: + result = [] + if not result: + source = Config.ArgsSource.INVOCATION_DIR + result = [str(invocation_dir)] + return result, source + + def _preparse(self, args: list[str], addopts: bool = True) -> None: + if addopts: + env_addopts = os.environ.get("PYTEST_ADDOPTS", "") + if len(env_addopts): + args[:] = ( + self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS") + + args + ) + self._initini(args) + if addopts: + args[:] = ( + self._validate_args(self.getini("addopts"), "via addopts config") + args + ) + + self.known_args_namespace = self._parser.parse_known_args( + args, namespace=copy.copy(self.option) + ) + self._checkversion() + self._consider_importhook(args) + self.pluginmanager.consider_preparse(args, exclude_only=False) + if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): + # Don't autoload from distribution package entry point. Only + # explicitly specified plugins are going to be loaded. + self.pluginmanager.load_setuptools_entrypoints("pytest11") + self.pluginmanager.consider_env() + + self.known_args_namespace = self._parser.parse_known_args( + args, namespace=copy.copy(self.known_args_namespace) + ) + + self._validate_plugins() + self._warn_about_skipped_plugins() + + if self.known_args_namespace.confcutdir is None: + if self.inipath is not None: + confcutdir = str(self.inipath.parent) + else: + confcutdir = str(self.rootpath) + self.known_args_namespace.confcutdir = confcutdir + try: + self.hook.pytest_load_initial_conftests( + early_config=self, args=args, parser=self._parser + ) + except ConftestImportFailure as e: + if self.known_args_namespace.help or self.known_args_namespace.version: + # we don't want to prevent --help/--version to work + # so just let is pass and print a warning at the end + self.issue_config_time_warning( + PytestConfigWarning(f"could not load initial conftests: {e.path}"), + stacklevel=2, + ) + else: + raise + + @hookimpl(wrapper=True) + def pytest_collection(self) -> Generator[None, object, object]: + # Validate invalid ini keys after collection is done so we take in account + # options added by late-loading conftest files. + try: + return (yield) + finally: + self._validate_config_options() + + def _checkversion(self) -> None: + import pytest + + minver = self.inicfg.get("minversion", None) + if minver: + # Imported lazily to improve start-up time. + from packaging.version import Version + + if not isinstance(minver, str): + raise pytest.UsageError( + f"{self.inipath}: 'minversion' must be a single value" + ) + + if Version(minver) > Version(pytest.__version__): + raise pytest.UsageError( + f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'" + ) + + def _validate_config_options(self) -> None: + for key in sorted(self._get_unknown_ini_keys()): + self._warn_or_fail_if_strict(f"Unknown config option: {key}\n") + + def _validate_plugins(self) -> None: + required_plugins = sorted(self.getini("required_plugins")) + if not required_plugins: + return + + # Imported lazily to improve start-up time. + from packaging.requirements import InvalidRequirement + from packaging.requirements import Requirement + from packaging.version import Version + + plugin_info = self.pluginmanager.list_plugin_distinfo() + plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} + + missing_plugins = [] + for required_plugin in required_plugins: + try: + req = Requirement(required_plugin) + except InvalidRequirement: + missing_plugins.append(required_plugin) + continue + + if req.name not in plugin_dist_info: + missing_plugins.append(required_plugin) + elif not req.specifier.contains( + Version(plugin_dist_info[req.name]), prereleases=True + ): + missing_plugins.append(required_plugin) + + if missing_plugins: + raise UsageError( + "Missing required plugins: {}".format(", ".join(missing_plugins)), + ) + + def _warn_or_fail_if_strict(self, message: str) -> None: + if self.known_args_namespace.strict_config: + raise UsageError(message) + + self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) + + def _get_unknown_ini_keys(self) -> list[str]: + parser_inicfg = self._parser._inidict + return [name for name in self.inicfg if name not in parser_inicfg] + + def parse(self, args: list[str], addopts: bool = True) -> None: + # Parse given cmdline arguments into this config object. + assert ( + self.args == [] + ), "can only parse cmdline args at most once per Config object" + self.hook.pytest_addhooks.call_historic( + kwargs=dict(pluginmanager=self.pluginmanager) + ) + self._preparse(args, addopts=addopts) + self._parser.after_preparse = True # type: ignore + try: + args = self._parser.parse_setoption( + args, self.option, namespace=self.option + ) + self.args, self.args_source = self._decide_args( + args=args, + pyargs=self.known_args_namespace.pyargs, + testpaths=self.getini("testpaths"), + invocation_dir=self.invocation_params.dir, + rootpath=self.rootpath, + warn=True, + ) + except PrintHelp: + pass + + def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: + """Issue and handle a warning during the "configure" stage. + + During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item`` + function because it is not possible to have hook wrappers around ``pytest_configure``. + + This function is mainly intended for plugins that need to issue warnings during + ``pytest_configure`` (or similar stages). + + :param warning: The warning instance. + :param stacklevel: stacklevel forwarded to warnings.warn. + """ + if self.pluginmanager.is_blocked("warnings"): + return + + cmdline_filters = self.known_args_namespace.pythonwarnings or [] + config_filters = self.getini("filterwarnings") + + with warnings.catch_warnings(record=True) as records: + warnings.simplefilter("always", type(warning)) + apply_warning_filters(config_filters, cmdline_filters) + warnings.warn(warning, stacklevel=stacklevel) + + if records: + frame = sys._getframe(stacklevel - 1) + location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + self.hook.pytest_warning_recorded.call_historic( + kwargs=dict( + warning_message=records[0], + when="config", + nodeid="", + location=location, + ) + ) + + def addinivalue_line(self, name: str, line: str) -> None: + """Add a line to an ini-file option. The option must have been + declared but might not yet be set in which case the line becomes + the first line in its value.""" + x = self.getini(name) + assert isinstance(x, list) + x.append(line) # modifies the cached list inline + + def getini(self, name: str): + """Return configuration value from an :ref:`ini file `. + + If a configuration value is not defined in an + :ref:`ini file `, then the ``default`` value provided while + registering the configuration through + :func:`parser.addini ` will be returned. + Please note that you can even provide ``None`` as a valid + default value. + + If ``default`` is not provided while registering using + :func:`parser.addini `, then a default value + based on the ``type`` parameter passed to + :func:`parser.addini ` will be returned. + The default values based on ``type`` are: + ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]`` + ``bool`` : ``False`` + ``string`` : empty string ``""`` + + If neither the ``default`` nor the ``type`` parameter is passed + while registering the configuration through + :func:`parser.addini `, then the configuration + is treated as a string and a default empty string '' is returned. + + If the specified name hasn't been registered through a prior + :func:`parser.addini ` call (usually from a + plugin), a ValueError is raised. + """ + try: + return self._inicache[name] + except KeyError: + self._inicache[name] = val = self._getini(name) + return val + + # Meant for easy monkeypatching by legacypath plugin. + # Can be inlined back (with no cover removed) once legacypath is gone. + def _getini_unknown_type(self, name: str, type: str, value: str | list[str]): + msg = f"unknown configuration type: {type}" + raise ValueError(msg, value) # pragma: no cover + + def _getini(self, name: str): + try: + description, type, default = self._parser._inidict[name] + except KeyError as e: + raise ValueError(f"unknown configuration value: {name!r}") from e + override_value = self._get_override_ini_value(name) + if override_value is None: + try: + value = self.inicfg[name] + except KeyError: + return default + else: + value = override_value + # Coerce the values based on types. + # + # Note: some coercions are only required if we are reading from .ini files, because + # the file format doesn't contain type information, but when reading from toml we will + # get either str or list of str values (see _parse_ini_config_from_pyproject_toml). + # For example: + # + # ini: + # a_line_list = "tests acceptance" + # in this case, we need to split the string to obtain a list of strings. + # + # toml: + # a_line_list = ["tests", "acceptance"] + # in this case, we already have a list ready to use. + # + if type == "paths": + dp = ( + self.inipath.parent + if self.inipath is not None + else self.invocation_params.dir + ) + input_values = shlex.split(value) if isinstance(value, str) else value + return [dp / x for x in input_values] + elif type == "args": + return shlex.split(value) if isinstance(value, str) else value + elif type == "linelist": + if isinstance(value, str): + return [t for t in map(lambda x: x.strip(), value.split("\n")) if t] + else: + return value + elif type == "bool": + return _strtobool(str(value).strip()) + elif type == "string": + return value + elif type is None: + return value + else: + return self._getini_unknown_type(name, type, value) + + def _getconftest_pathlist( + self, name: str, path: pathlib.Path + ) -> list[pathlib.Path] | None: + try: + mod, relroots = self.pluginmanager._rget_with_confmod(name, path) + except KeyError: + return None + assert mod.__file__ is not None + modpath = pathlib.Path(mod.__file__).parent + values: list[pathlib.Path] = [] + for relroot in relroots: + if isinstance(relroot, os.PathLike): + relroot = pathlib.Path(relroot) + else: + relroot = relroot.replace("/", os.sep) + relroot = absolutepath(modpath / relroot) + values.append(relroot) + return values + + def _get_override_ini_value(self, name: str) -> str | None: + value = None + # override_ini is a list of "ini=value" options. + # Always use the last item if multiple values are set for same ini-name, + # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2. + for ini_config in self._override_ini: + try: + key, user_ini_value = ini_config.split("=", 1) + except ValueError as e: + raise UsageError( + f"-o/--override-ini expects option=value style (got: {ini_config!r})." + ) from e + else: + if key == name: + value = user_ini_value + return value + + def getoption(self, name: str, default=notset, skip: bool = False): + """Return command line option value. + + :param name: Name of the option. You may also specify + the literal ``--OPT`` option instead of the "dest" option name. + :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`. + Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``. + :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value. + Note that even if ``True``, if a default was specified it will be returned instead of a skip. + """ + name = self._opt2dest.get(name, name) + try: + val = getattr(self.option, name) + if val is None and skip: + raise AttributeError(name) + return val + except AttributeError as e: + if default is not notset: + return default + if skip: + import pytest + + pytest.skip(f"no {name!r} option found") + raise ValueError(f"no option named {name!r}") from e + + def getvalue(self, name: str, path=None): + """Deprecated, use getoption() instead.""" + return self.getoption(name) + + def getvalueorskip(self, name: str, path=None): + """Deprecated, use getoption(skip=True) instead.""" + return self.getoption(name, skip=True) + + #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`). + VERBOSITY_ASSERTIONS: Final = "assertions" + #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`). + VERBOSITY_TEST_CASES: Final = "test_cases" + _VERBOSITY_INI_DEFAULT: Final = "auto" + + def get_verbosity(self, verbosity_type: str | None = None) -> int: + r"""Retrieve the verbosity level for a fine-grained verbosity type. + + :param verbosity_type: Verbosity type to get level for. If a level is + configured for the given type, that value will be returned. If the + given type is not a known verbosity type, the global verbosity + level will be returned. If the given type is None (default), the + global verbosity level will be returned. + + To configure a level for a fine-grained verbosity type, the + configuration file should have a setting for the configuration name + and a numeric value for the verbosity level. A special value of "auto" + can be used to explicitly use the global verbosity level. + + Example: + + .. code-block:: ini + + # content of pytest.ini + [pytest] + verbosity_assertions = 2 + + .. code-block:: console + + pytest -v + + .. code-block:: python + + print(config.get_verbosity()) # 1 + print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS)) # 2 + """ + global_level = self.getoption("verbose", default=0) + assert isinstance(global_level, int) + if verbosity_type is None: + return global_level + + ini_name = Config._verbosity_ini_name(verbosity_type) + if ini_name not in self._parser._inidict: + return global_level + + level = self.getini(ini_name) + if level == Config._VERBOSITY_INI_DEFAULT: + return global_level + + return int(level) + + @staticmethod + def _verbosity_ini_name(verbosity_type: str) -> str: + return f"verbosity_{verbosity_type}" + + @staticmethod + def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None: + """Add a output verbosity configuration option for the given output type. + + :param parser: Parser for command line arguments and ini-file values. + :param verbosity_type: Fine-grained verbosity category. + :param help: Description of the output this type controls. + + The value should be retrieved via a call to + :py:func:`config.get_verbosity(type) `. + """ + parser.addini( + Config._verbosity_ini_name(verbosity_type), + help=help, + type="string", + default=Config._VERBOSITY_INI_DEFAULT, + ) + + def _warn_about_missing_assertion(self, mode: str) -> None: + if not _assertion_supported(): + if mode == "plain": + warning_text = ( + "ASSERTIONS ARE NOT EXECUTED" + " and FAILING TESTS WILL PASS. Are you" + " using python -O?" + ) + else: + warning_text = ( + "assertions not in test modules or" + " plugins will be ignored" + " because assert statements are not executed " + "by the underlying Python interpreter " + "(are you using python -O?)\n" + ) + self.issue_config_time_warning( + PytestConfigWarning(warning_text), + stacklevel=3, + ) + + def _warn_about_skipped_plugins(self) -> None: + for module_name, msg in self.pluginmanager.skipped_plugins: + self.issue_config_time_warning( + PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"), + stacklevel=2, + ) + + +def _assertion_supported() -> bool: + try: + assert False + except AssertionError: + return True + else: + return False # type: ignore[unreachable] + + +def create_terminal_writer( + config: Config, file: TextIO | None = None +) -> TerminalWriter: + """Create a TerminalWriter instance configured according to the options + in the config object. + + Every code which requires a TerminalWriter object and has access to a + config object should use this function. + """ + tw = TerminalWriter(file=file) + + if config.option.color == "yes": + tw.hasmarkup = True + elif config.option.color == "no": + tw.hasmarkup = False + + if config.option.code_highlight == "yes": + tw.code_highlight = True + elif config.option.code_highlight == "no": + tw.code_highlight = False + + return tw + + +def _strtobool(val: str) -> bool: + """Convert a string representation of truth to True or False. + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + + .. note:: Copied from distutils.util. + """ + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError(f"invalid truth value {val!r}") + + +@lru_cache(maxsize=50) +def parse_warning_filter( + arg: str, *, escape: bool +) -> tuple[warnings._ActionKind, str, type[Warning], str, int]: + """Parse a warnings filter string. + + This is copied from warnings._setoption with the following changes: + + * Does not apply the filter. + * Escaping is optional. + * Raises UsageError so we get nice error messages on failure. + """ + __tracebackhide__ = True + error_template = dedent( + f"""\ + while parsing the following warning configuration: + + {arg} + + This error occurred: + + {{error}} + """ + ) + + parts = arg.split(":") + if len(parts) > 5: + doc_url = ( + "https://docs.python.org/3/library/warnings.html#describing-warning-filters" + ) + error = dedent( + f"""\ + Too many fields ({len(parts)}), expected at most 5 separated by colons: + + action:message:category:module:line + + For more information please consult: {doc_url} + """ + ) + raise UsageError(error_template.format(error=error)) + + while len(parts) < 5: + parts.append("") + action_, message, category_, module, lineno_ = (s.strip() for s in parts) + try: + action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined] + except warnings._OptionError as e: + raise UsageError(error_template.format(error=str(e))) from None + try: + category: type[Warning] = _resolve_warning_category(category_) + except Exception: + exc_info = ExceptionInfo.from_current() + exception_text = exc_info.getrepr(style="native") + raise UsageError(error_template.format(error=exception_text)) from None + if message and escape: + message = re.escape(message) + if module and escape: + module = re.escape(module) + r"\Z" + if lineno_: + try: + lineno = int(lineno_) + if lineno < 0: + raise ValueError("number is negative") + except ValueError as e: + raise UsageError( + error_template.format(error=f"invalid lineno {lineno_!r}: {e}") + ) from None + else: + lineno = 0 + return action, message, category, module, lineno + + +def _resolve_warning_category(category: str) -> type[Warning]: + """ + Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors) + propagate so we can get access to their tracebacks (#9218). + """ + __tracebackhide__ = True + if not category: + return Warning + + if "." not in category: + import builtins as m + + klass = category + else: + module, _, klass = category.rpartition(".") + m = __import__(module, None, None, [klass]) + cat = getattr(m, klass) + if not issubclass(cat, Warning): + raise UsageError(f"{cat} is not a Warning subclass") + return cast(Type[Warning], cat) + + +def apply_warning_filters( + config_filters: Iterable[str], cmdline_filters: Iterable[str] +) -> None: + """Applies pytest-configured filters to the warnings module""" + # Filters should have this precedence: cmdline options, config. + # Filters should be applied in the inverse order of precedence. + for arg in config_filters: + warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) + + for arg in cmdline_filters: + warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) diff --git a/.venv/Lib/site-packages/_pytest/config/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/config/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..548380895f506649fff1d426ed37b8aba517c8e6 GIT binary patch literal 81317 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

M!7wY)cv5ai6c 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/config/__pycache__/argparsing.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/config/__pycache__/argparsing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1af8a8801125a12b04fa905a1f9b56f480d3d584 GIT binary patch literal 26047 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 J>X0(u{{w)mvEBdx literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/config/argparsing.py b/.venv/Lib/site-packages/_pytest/config/argparsing.py new file mode 100644 index 0000000..85aa463 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/config/argparsing.py @@ -0,0 +1,551 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import argparse +from gettext import gettext +import os +import sys +from typing import Any +from typing import Callable +from typing import cast +from typing import final +from typing import List +from typing import Literal +from typing import Mapping +from typing import NoReturn +from typing import Sequence + +import _pytest._io +from _pytest.config.exceptions import UsageError +from _pytest.deprecated import check_ispytest + + +FILE_OR_DIR = "file_or_dir" + + +class NotSet: + def __repr__(self) -> str: + return "" + + +NOT_SET = NotSet() + + +@final +class Parser: + """Parser for command line arguments and ini-file values. + + :ivar extra_info: Dict of generic param -> value to display in case + there's an error processing the command line arguments. + """ + + prog: str | None = None + + def __init__( + self, + usage: str | None = None, + processopt: Callable[[Argument], None] | None = None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) + self._groups: list[OptionGroup] = [] + self._processopt = processopt + self._usage = usage + self._inidict: dict[str, tuple[str, str | None, Any]] = {} + self._ininames: list[str] = [] + self.extra_info: dict[str, Any] = {} + + def processoption(self, option: Argument) -> None: + if self._processopt: + if option.dest: + self._processopt(option) + + def getgroup( + self, name: str, description: str = "", after: str | None = None + ) -> OptionGroup: + """Get (or create) a named option Group. + + :param name: Name of the option group. + :param description: Long description for --help output. + :param after: Name of another group, used for ordering --help output. + :returns: The option group. + + The returned group object has an ``addoption`` method with the same + signature as :func:`parser.addoption ` but + will be shown in the respective group in the output of + ``pytest --help``. + """ + for group in self._groups: + if group.name == name: + return group + group = OptionGroup(name, description, parser=self, _ispytest=True) + i = 0 + for i, grp in enumerate(self._groups): + if grp.name == after: + break + self._groups.insert(i + 1, group) + return group + + def addoption(self, *opts: str, **attrs: Any) -> None: + """Register a command line option. + + :param opts: + Option names, can be short or long options. + :param attrs: + Same attributes as the argparse library's :meth:`add_argument() + ` function accepts. + + After command line parsing, options are available on the pytest config + object via ``config.option.NAME`` where ``NAME`` is usually set + by passing a ``dest`` attribute, for example + ``addoption("--long", dest="NAME", ...)``. + """ + self._anonymous.addoption(*opts, **attrs) + + def parse( + self, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> argparse.Namespace: + from _pytest._argcomplete import try_argcomplete + + self.optparser = self._getparser() + try_argcomplete(self.optparser) + strargs = [os.fspath(x) for x in args] + return self.optparser.parse_args(strargs, namespace=namespace) + + def _getparser(self) -> MyOptionParser: + from _pytest._argcomplete import filescompleter + + optparser = MyOptionParser(self, self.extra_info, prog=self.prog) + groups = [*self._groups, self._anonymous] + for group in groups: + if group.options: + desc = group.description or group.name + arggroup = optparser.add_argument_group(desc) + for option in group.options: + n = option.names() + a = option.attrs() + arggroup.add_argument(*n, **a) + file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*") + # bash like autocompletion for dirs (appending '/') + # Type ignored because typeshed doesn't know about argcomplete. + file_or_dir_arg.completer = filescompleter # type: ignore + return optparser + + def parse_setoption( + self, + args: Sequence[str | os.PathLike[str]], + option: argparse.Namespace, + namespace: argparse.Namespace | None = None, + ) -> list[str]: + parsedoption = self.parse(args, namespace=namespace) + for name, value in parsedoption.__dict__.items(): + setattr(option, name, value) + return cast(List[str], getattr(parsedoption, FILE_OR_DIR)) + + def parse_known_args( + self, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> argparse.Namespace: + """Parse the known arguments at this point. + + :returns: An argparse namespace object. + """ + return self.parse_known_and_unknown_args(args, namespace=namespace)[0] + + def parse_known_and_unknown_args( + self, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> tuple[argparse.Namespace, list[str]]: + """Parse the known arguments at this point, and also return the + remaining unknown arguments. + + :returns: + A tuple containing an argparse namespace object for the known + arguments, and a list of the unknown arguments. + """ + optparser = self._getparser() + strargs = [os.fspath(x) for x in args] + return optparser.parse_known_args(strargs, namespace=namespace) + + def addini( + self, + name: str, + help: str, + type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] + | None = None, + default: Any = NOT_SET, + ) -> None: + """Register an ini-file option. + + :param name: + Name of the ini-variable. + :param type: + Type of the variable. Can be: + + * ``string``: a string + * ``bool``: a boolean + * ``args``: a list of strings, separated as in a shell + * ``linelist``: a list of strings, separated by line breaks + * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell + * ``pathlist``: a list of ``py.path``, separated as in a shell + + For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file. + In case the execution is happening without an ini-file defined, + they will be considered relative to the current working directory (for example with ``--override-ini``). + + .. versionadded:: 7.0 + The ``paths`` variable type. + + .. versionadded:: 8.1 + Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of an ini-file. + + Defaults to ``string`` if ``None`` or not passed. + :param default: + Default value if no ini-file option exists but is queried. + + The value of ini-variables can be retrieved via a call to + :py:func:`config.getini(name) `. + """ + assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool") + if default is NOT_SET: + default = get_ini_default_for_type(type) + + self._inidict[name] = (help, type, default) + self._ininames.append(name) + + +def get_ini_default_for_type( + type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None, +) -> Any: + """ + Used by addini to get the default value for a given ini-option type, when + default is not supplied. + """ + if type is None: + return "" + elif type in ("paths", "pathlist", "args", "linelist"): + return [] + elif type == "bool": + return False + else: + return "" + + +class ArgumentError(Exception): + """Raised if an Argument instance is created with invalid or + inconsistent arguments.""" + + def __init__(self, msg: str, option: Argument | str) -> None: + self.msg = msg + self.option_id = str(option) + + def __str__(self) -> str: + if self.option_id: + return f"option {self.option_id}: {self.msg}" + else: + return self.msg + + +class Argument: + """Class that mimics the necessary behaviour of optparse.Option. + + It's currently a least effort implementation and ignoring choices + and integer prefixes. + + https://docs.python.org/3/library/optparse.html#optparse-standard-option-types + """ + + def __init__(self, *names: str, **attrs: Any) -> None: + """Store params in private vars for use in add_argument.""" + self._attrs = attrs + self._short_opts: list[str] = [] + self._long_opts: list[str] = [] + try: + self.type = attrs["type"] + except KeyError: + pass + try: + # Attribute existence is tested in Config._processopt. + self.default = attrs["default"] + except KeyError: + pass + self._set_opt_strings(names) + dest: str | None = attrs.get("dest") + if dest: + self.dest = dest + elif self._long_opts: + self.dest = self._long_opts[0][2:].replace("-", "_") + else: + try: + self.dest = self._short_opts[0][1:] + except IndexError as e: + self.dest = "???" # Needed for the error repr. + raise ArgumentError("need a long or short option", self) from e + + def names(self) -> list[str]: + return self._short_opts + self._long_opts + + def attrs(self) -> Mapping[str, Any]: + # Update any attributes set by processopt. + attrs = "default dest help".split() + attrs.append(self.dest) + for attr in attrs: + try: + self._attrs[attr] = getattr(self, attr) + except AttributeError: + pass + return self._attrs + + def _set_opt_strings(self, opts: Sequence[str]) -> None: + """Directly from optparse. + + Might not be necessary as this is passed to argparse later on. + """ + for opt in opts: + if len(opt) < 2: + raise ArgumentError( + f"invalid option string {opt!r}: " + "must be at least two characters long", + self, + ) + elif len(opt) == 2: + if not (opt[0] == "-" and opt[1] != "-"): + raise ArgumentError( + f"invalid short option string {opt!r}: " + "must be of the form -x, (x any non-dash char)", + self, + ) + self._short_opts.append(opt) + else: + if not (opt[0:2] == "--" and opt[2] != "-"): + raise ArgumentError( + f"invalid long option string {opt!r}: " + "must start with --, followed by non-dash", + self, + ) + self._long_opts.append(opt) + + def __repr__(self) -> str: + args: list[str] = [] + if self._short_opts: + args += ["_short_opts: " + repr(self._short_opts)] + if self._long_opts: + args += ["_long_opts: " + repr(self._long_opts)] + args += ["dest: " + repr(self.dest)] + if hasattr(self, "type"): + args += ["type: " + repr(self.type)] + if hasattr(self, "default"): + args += ["default: " + repr(self.default)] + return "Argument({})".format(", ".join(args)) + + +class OptionGroup: + """A group of options shown in its own section.""" + + def __init__( + self, + name: str, + description: str = "", + parser: Parser | None = None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self.name = name + self.description = description + self.options: list[Argument] = [] + self.parser = parser + + def addoption(self, *opts: str, **attrs: Any) -> None: + """Add an option to this group. + + If a shortened version of a long option is specified, it will + be suppressed in the help. ``addoption('--twowords', '--two-words')`` + results in help showing ``--two-words`` only, but ``--twowords`` gets + accepted **and** the automatic destination is in ``args.twowords``. + + :param opts: + Option names, can be short or long options. + :param attrs: + Same attributes as the argparse library's :meth:`add_argument() + ` function accepts. + """ + conflict = set(opts).intersection( + name for opt in self.options for name in opt.names() + ) + if conflict: + raise ValueError(f"option names {conflict} already added") + option = Argument(*opts, **attrs) + self._addoption_instance(option, shortupper=False) + + def _addoption(self, *opts: str, **attrs: Any) -> None: + option = Argument(*opts, **attrs) + self._addoption_instance(option, shortupper=True) + + def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None: + if not shortupper: + for opt in option._short_opts: + if opt[0] == "-" and opt[1].islower(): + raise ValueError("lowercase shortoptions reserved") + if self.parser: + self.parser.processoption(option) + self.options.append(option) + + +class MyOptionParser(argparse.ArgumentParser): + def __init__( + self, + parser: Parser, + extra_info: dict[str, Any] | None = None, + prog: str | None = None, + ) -> None: + self._parser = parser + super().__init__( + prog=prog, + usage=parser._usage, + add_help=False, + formatter_class=DropShorterLongHelpFormatter, + allow_abbrev=False, + fromfile_prefix_chars="@", + ) + # extra_info is a dict of (param -> value) to display if there's + # an usage error to provide more contextual information to the user. + self.extra_info = extra_info if extra_info else {} + + def error(self, message: str) -> NoReturn: + """Transform argparse error message into UsageError.""" + msg = f"{self.prog}: error: {message}" + + if hasattr(self._parser, "_config_source_hint"): + msg = f"{msg} ({self._parser._config_source_hint})" + + raise UsageError(self.format_usage() + msg) + + # Type ignored because typeshed has a very complex type in the superclass. + def parse_args( # type: ignore + self, + args: Sequence[str] | None = None, + namespace: argparse.Namespace | None = None, + ) -> argparse.Namespace: + """Allow splitting of positional arguments.""" + parsed, unrecognized = self.parse_known_args(args, namespace) + if unrecognized: + for arg in unrecognized: + if arg and arg[0] == "-": + lines = [ + "unrecognized arguments: {}".format(" ".join(unrecognized)) + ] + for k, v in sorted(self.extra_info.items()): + lines.append(f" {k}: {v}") + self.error("\n".join(lines)) + getattr(parsed, FILE_OR_DIR).extend(unrecognized) + return parsed + + if sys.version_info < (3, 9): # pragma: no cover + # Backport of https://github.com/python/cpython/pull/14316 so we can + # disable long --argument abbreviations without breaking short flags. + def _parse_optional( + self, arg_string: str + ) -> tuple[argparse.Action | None, str, str | None] | None: + if not arg_string: + return None + if arg_string[0] not in self.prefix_chars: + return None + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + if len(arg_string) == 1: + return None + if "=" in arg_string: + option_string, explicit_arg = arg_string.split("=", 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + if self.allow_abbrev or not arg_string.startswith("--"): + option_tuples = self._get_option_tuples(arg_string) + if len(option_tuples) > 1: + msg = gettext( + "ambiguous option: %(option)s could match %(matches)s" + ) + options = ", ".join(option for _, option, _ in option_tuples) + self.error(msg % {"option": arg_string, "matches": options}) + elif len(option_tuples) == 1: + (option_tuple,) = option_tuples + return option_tuple + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + if " " in arg_string: + return None + return None, arg_string, None + + +class DropShorterLongHelpFormatter(argparse.HelpFormatter): + """Shorten help for long options that differ only in extra hyphens. + + - Collapse **long** options that are the same except for extra hyphens. + - Shortcut if there are only two options and one of them is a short one. + - Cache result on the action object as this is called at least 2 times. + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + # Use more accurate terminal width. + if "width" not in kwargs: + kwargs["width"] = _pytest._io.get_terminal_width() + super().__init__(*args, **kwargs) + + def _format_action_invocation(self, action: argparse.Action) -> str: + orgstr = super()._format_action_invocation(action) + if orgstr and orgstr[0] != "-": # only optional arguments + return orgstr + res: str | None = getattr(action, "_formatted_action_invocation", None) + if res: + return res + options = orgstr.split(", ") + if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2): + # a shortcut for '-h, --help' or '--abc', '-a' + action._formatted_action_invocation = orgstr # type: ignore + return orgstr + return_list = [] + short_long: dict[str, str] = {} + for option in options: + if len(option) == 2 or option[2] == " ": + continue + if not option.startswith("--"): + raise ArgumentError( + f'long optional argument without "--": [{option}]', option + ) + xxoption = option[2:] + shortened = xxoption.replace("-", "") + if shortened not in short_long or len(short_long[shortened]) < len( + xxoption + ): + short_long[shortened] = xxoption + # now short_long has been filled out to the longest with dashes + # **and** we keep the right option ordering from add_argument + for option in options: + if len(option) == 2 or option[2] == " ": + return_list.append(option) + if option[2:] == short_long.get(option.replace("-", "")): + return_list.append(option.replace(" ", "=", 1)) + formatted_action_invocation = ", ".join(return_list) + action._formatted_action_invocation = formatted_action_invocation # type: ignore + return formatted_action_invocation + + def _split_lines(self, text, width): + """Wrap lines after splitting on original newlines. + + This allows to have explicit line breaks in the help text. + """ + import textwrap + + lines = [] + for line in text.splitlines(): + lines.extend(textwrap.wrap(line.strip(), width)) + return lines diff --git a/.venv/Lib/site-packages/_pytest/config/compat.py b/.venv/Lib/site-packages/_pytest/config/compat.py new file mode 100644 index 0000000..2856d85 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/config/compat.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import functools +from pathlib import Path +from typing import Any +from typing import Mapping +import warnings + +import pluggy + +from ..compat import LEGACY_PATH +from ..compat import legacy_path +from ..deprecated import HOOK_LEGACY_PATH_ARG + + +# hookname: (Path, LEGACY_PATH) +imply_paths_hooks: Mapping[str, tuple[str, str]] = { + "pytest_ignore_collect": ("collection_path", "path"), + "pytest_collect_file": ("file_path", "path"), + "pytest_pycollect_makemodule": ("module_path", "path"), + "pytest_report_header": ("start_path", "startdir"), + "pytest_report_collectionfinish": ("start_path", "startdir"), +} + + +def _check_path(path: Path, fspath: LEGACY_PATH) -> None: + if Path(fspath) != path: + raise ValueError( + f"Path({fspath!r}) != {path!r}\n" + "if both path and fspath are given they need to be equal" + ) + + +class PathAwareHookProxy: + """ + this helper wraps around hook callers + until pluggy supports fixingcalls, this one will do + + it currently doesn't return full hook caller proxies for fixed hooks, + this may have to be changed later depending on bugs + """ + + def __init__(self, hook_relay: pluggy.HookRelay) -> None: + self._hook_relay = hook_relay + + def __dir__(self) -> list[str]: + return dir(self._hook_relay) + + def __getattr__(self, key: str) -> pluggy.HookCaller: + hook: pluggy.HookCaller = getattr(self._hook_relay, key) + if key not in imply_paths_hooks: + self.__dict__[key] = hook + return hook + else: + path_var, fspath_var = imply_paths_hooks[key] + + @functools.wraps(hook) + def fixed_hook(**kw: Any) -> Any: + path_value: Path | None = kw.pop(path_var, None) + fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None) + if fspath_value is not None: + warnings.warn( + HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg=fspath_var, pathlib_path_arg=path_var + ), + stacklevel=2, + ) + if path_value is not None: + if fspath_value is not None: + _check_path(path_value, fspath_value) + else: + fspath_value = legacy_path(path_value) + else: + assert fspath_value is not None + path_value = Path(fspath_value) + + kw[path_var] = path_value + kw[fspath_var] = fspath_value + return hook(**kw) + + fixed_hook.name = hook.name # type: ignore[attr-defined] + fixed_hook.spec = hook.spec # type: ignore[attr-defined] + fixed_hook.__name__ = key + self.__dict__[key] = fixed_hook + return fixed_hook # type: ignore[return-value] diff --git a/.venv/Lib/site-packages/_pytest/config/exceptions.py b/.venv/Lib/site-packages/_pytest/config/exceptions.py new file mode 100644 index 0000000..90108ec --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/config/exceptions.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from typing import final + + +@final +class UsageError(Exception): + """Error in pytest usage or invocation.""" + + +class PrintHelp(Exception): + """Raised when pytest should print its help to skip the rest of the + argument parsing and validation.""" diff --git a/.venv/Lib/site-packages/_pytest/config/findpaths.py b/.venv/Lib/site-packages/_pytest/config/findpaths.py new file mode 100644 index 0000000..ce4c990 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/config/findpaths.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +import os +from pathlib import Path +import sys +from typing import Iterable +from typing import Sequence + +import iniconfig + +from .exceptions import UsageError +from _pytest.outcomes import fail +from _pytest.pathlib import absolutepath +from _pytest.pathlib import commonpath +from _pytest.pathlib import safe_exists + + +def _parse_ini_config(path: Path) -> iniconfig.IniConfig: + """Parse the given generic '.ini' file using legacy IniConfig parser, returning + the parsed object. + + Raise UsageError if the file cannot be parsed. + """ + try: + return iniconfig.IniConfig(str(path)) + except iniconfig.ParseError as exc: + raise UsageError(str(exc)) from exc + + +def load_config_dict_from_file( + filepath: Path, +) -> dict[str, str | list[str]] | None: + """Load pytest configuration from the given file path, if supported. + + Return None if the file does not contain valid pytest configuration. + """ + # Configuration from ini files are obtained from the [pytest] section, if present. + if filepath.suffix == ".ini": + iniconfig = _parse_ini_config(filepath) + + if "pytest" in iniconfig: + return dict(iniconfig["pytest"].items()) + else: + # "pytest.ini" files are always the source of configuration, even if empty. + if filepath.name == "pytest.ini": + return {} + + # '.cfg' files are considered if they contain a "[tool:pytest]" section. + elif filepath.suffix == ".cfg": + iniconfig = _parse_ini_config(filepath) + + if "tool:pytest" in iniconfig.sections: + return dict(iniconfig["tool:pytest"].items()) + elif "pytest" in iniconfig.sections: + # If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that + # plain "[pytest]" sections in setup.cfg files is no longer supported (#3086). + fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False) + + # '.toml' files are considered if they contain a [tool.pytest.ini_options] table. + elif filepath.suffix == ".toml": + if sys.version_info >= (3, 11): + import tomllib + else: + import tomli as tomllib + + toml_text = filepath.read_text(encoding="utf-8") + try: + config = tomllib.loads(toml_text) + except tomllib.TOMLDecodeError as exc: + raise UsageError(f"{filepath}: {exc}") from exc + + result = config.get("tool", {}).get("pytest", {}).get("ini_options", None) + if result is not None: + # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc), + # however we need to convert all scalar values to str for compatibility with the rest + # of the configuration system, which expects strings only. + def make_scalar(v: object) -> str | list[str]: + return v if isinstance(v, list) else str(v) + + return {k: make_scalar(v) for k, v in result.items()} + + return None + + +def locate_config( + invocation_dir: Path, + args: Iterable[Path], +) -> tuple[Path | None, Path | None, dict[str, str | list[str]]]: + """Search in the list of arguments for a valid ini-file for pytest, + and return a tuple of (rootdir, inifile, cfg-dict).""" + config_names = [ + "pytest.ini", + ".pytest.ini", + "pyproject.toml", + "tox.ini", + "setup.cfg", + ] + args = [x for x in args if not str(x).startswith("-")] + if not args: + args = [invocation_dir] + found_pyproject_toml: Path | None = None + for arg in args: + argpath = absolutepath(arg) + for base in (argpath, *argpath.parents): + for config_name in config_names: + p = base / config_name + if p.is_file(): + if p.name == "pyproject.toml" and found_pyproject_toml is None: + found_pyproject_toml = p + ini_config = load_config_dict_from_file(p) + if ini_config is not None: + return base, p, ini_config + if found_pyproject_toml is not None: + return found_pyproject_toml.parent, found_pyproject_toml, {} + return None, None, {} + + +def get_common_ancestor( + invocation_dir: Path, + paths: Iterable[Path], +) -> Path: + common_ancestor: Path | None = None + for path in paths: + if not path.exists(): + continue + if common_ancestor is None: + common_ancestor = path + else: + if common_ancestor in path.parents or path == common_ancestor: + continue + elif path in common_ancestor.parents: + common_ancestor = path + else: + shared = commonpath(path, common_ancestor) + if shared is not None: + common_ancestor = shared + if common_ancestor is None: + common_ancestor = invocation_dir + elif common_ancestor.is_file(): + common_ancestor = common_ancestor.parent + return common_ancestor + + +def get_dirs_from_args(args: Iterable[str]) -> list[Path]: + def is_option(x: str) -> bool: + return x.startswith("-") + + def get_file_part_from_node_id(x: str) -> str: + return x.split("::")[0] + + def get_dir_from_path(path: Path) -> Path: + if path.is_dir(): + return path + return path.parent + + # These look like paths but may not exist + possible_paths = ( + absolutepath(get_file_part_from_node_id(arg)) + for arg in args + if not is_option(arg) + ) + + return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)] + + +CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead." + + +def determine_setup( + *, + inifile: str | None, + args: Sequence[str], + rootdir_cmd_arg: str | None, + invocation_dir: Path, +) -> tuple[Path, Path | None, dict[str, str | list[str]]]: + """Determine the rootdir, inifile and ini configuration values from the + command line arguments. + + :param inifile: + The `--inifile` command line argument, if given. + :param args: + The free command line arguments. + :param rootdir_cmd_arg: + The `--rootdir` command line argument, if given. + :param invocation_dir: + The working directory when pytest was invoked. + """ + rootdir = None + dirs = get_dirs_from_args(args) + if inifile: + inipath_ = absolutepath(inifile) + inipath: Path | None = inipath_ + inicfg = load_config_dict_from_file(inipath_) or {} + if rootdir_cmd_arg is None: + rootdir = inipath_.parent + else: + ancestor = get_common_ancestor(invocation_dir, dirs) + rootdir, inipath, inicfg = locate_config(invocation_dir, [ancestor]) + if rootdir is None and rootdir_cmd_arg is None: + for possible_rootdir in (ancestor, *ancestor.parents): + if (possible_rootdir / "setup.py").is_file(): + rootdir = possible_rootdir + break + else: + if dirs != [ancestor]: + rootdir, inipath, inicfg = locate_config(invocation_dir, dirs) + if rootdir is None: + rootdir = get_common_ancestor( + invocation_dir, [invocation_dir, ancestor] + ) + if is_fs_root(rootdir): + rootdir = ancestor + if rootdir_cmd_arg: + rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) + if not rootdir.is_dir(): + raise UsageError( + f"Directory '{rootdir}' not found. Check your '--rootdir' option." + ) + assert rootdir is not None + return rootdir, inipath, inicfg or {} + + +def is_fs_root(p: Path) -> bool: + r""" + Return True if the given path is pointing to the root of the + file system ("/" on Unix and "C:\\" on Windows for example). + """ + return os.path.splitdrive(str(p))[1] == os.sep diff --git a/.venv/Lib/site-packages/_pytest/debugging.py b/.venv/Lib/site-packages/_pytest/debugging.py new file mode 100644 index 0000000..2dfe321 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/debugging.py @@ -0,0 +1,385 @@ +# mypy: allow-untyped-defs +# ruff: noqa: T100 +"""Interactive debugging with PDB, the Python Debugger.""" + +from __future__ import annotations + +import argparse +import functools +import sys +import types +from typing import Any +from typing import Callable +from typing import Generator +import unittest + +from _pytest import outcomes +from _pytest._code import ExceptionInfo +from _pytest.capture import CaptureManager +from _pytest.config import Config +from _pytest.config import ConftestImportFailure +from _pytest.config import hookimpl +from _pytest.config import PytestPluginManager +from _pytest.config.argparsing import Parser +from _pytest.config.exceptions import UsageError +from _pytest.nodes import Node +from _pytest.reports import BaseReport +from _pytest.runner import CallInfo + + +def _validate_usepdb_cls(value: str) -> tuple[str, str]: + """Validate syntax of --pdbcls option.""" + try: + modname, classname = value.split(":") + except ValueError as e: + raise argparse.ArgumentTypeError( + f"{value!r} is not in the format 'modname:classname'" + ) from e + return (modname, classname) + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("general") + group._addoption( + "--pdb", + dest="usepdb", + action="store_true", + help="Start the interactive Python debugger on errors or KeyboardInterrupt", + ) + group._addoption( + "--pdbcls", + dest="usepdb_cls", + metavar="modulename:classname", + type=_validate_usepdb_cls, + help="Specify a custom interactive Python debugger for use with --pdb." + "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb", + ) + group._addoption( + "--trace", + dest="trace", + action="store_true", + help="Immediately break when running each test", + ) + + +def pytest_configure(config: Config) -> None: + import pdb + + if config.getvalue("trace"): + config.pluginmanager.register(PdbTrace(), "pdbtrace") + if config.getvalue("usepdb"): + config.pluginmanager.register(PdbInvoke(), "pdbinvoke") + + pytestPDB._saved.append( + (pdb.set_trace, pytestPDB._pluginmanager, pytestPDB._config) + ) + pdb.set_trace = pytestPDB.set_trace + pytestPDB._pluginmanager = config.pluginmanager + pytestPDB._config = config + + # NOTE: not using pytest_unconfigure, since it might get called although + # pytest_configure was not (if another plugin raises UsageError). + def fin() -> None: + ( + pdb.set_trace, + pytestPDB._pluginmanager, + pytestPDB._config, + ) = pytestPDB._saved.pop() + + config.add_cleanup(fin) + + +class pytestPDB: + """Pseudo PDB that defers to the real pdb.""" + + _pluginmanager: PytestPluginManager | None = None + _config: Config | None = None + _saved: list[ + tuple[Callable[..., None], PytestPluginManager | None, Config | None] + ] = [] + _recursive_debug = 0 + _wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None + + @classmethod + def _is_capturing(cls, capman: CaptureManager | None) -> str | bool: + if capman: + return capman.is_capturing() + return False + + @classmethod + def _import_pdb_cls(cls, capman: CaptureManager | None): + if not cls._config: + import pdb + + # Happens when using pytest.set_trace outside of a test. + return pdb.Pdb + + usepdb_cls = cls._config.getvalue("usepdb_cls") + + if cls._wrapped_pdb_cls and cls._wrapped_pdb_cls[0] == usepdb_cls: + return cls._wrapped_pdb_cls[1] + + if usepdb_cls: + modname, classname = usepdb_cls + + try: + __import__(modname) + mod = sys.modules[modname] + + # Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp). + parts = classname.split(".") + pdb_cls = getattr(mod, parts[0]) + for part in parts[1:]: + pdb_cls = getattr(pdb_cls, part) + except Exception as exc: + value = ":".join((modname, classname)) + raise UsageError( + f"--pdbcls: could not import {value!r}: {exc}" + ) from exc + else: + import pdb + + pdb_cls = pdb.Pdb + + wrapped_cls = cls._get_pdb_wrapper_class(pdb_cls, capman) + cls._wrapped_pdb_cls = (usepdb_cls, wrapped_cls) + return wrapped_cls + + @classmethod + def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None): + import _pytest.config + + class PytestPdbWrapper(pdb_cls): + _pytest_capman = capman + _continued = False + + def do_debug(self, arg): + cls._recursive_debug += 1 + ret = super().do_debug(arg) + cls._recursive_debug -= 1 + return ret + + def do_continue(self, arg): + ret = super().do_continue(arg) + if cls._recursive_debug == 0: + assert cls._config is not None + tw = _pytest.config.create_terminal_writer(cls._config) + tw.line() + + capman = self._pytest_capman + capturing = pytestPDB._is_capturing(capman) + if capturing: + if capturing == "global": + tw.sep(">", "PDB continue (IO-capturing resumed)") + else: + tw.sep( + ">", + f"PDB continue (IO-capturing resumed for {capturing})", + ) + assert capman is not None + capman.resume() + else: + tw.sep(">", "PDB continue") + assert cls._pluginmanager is not None + cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config, pdb=self) + self._continued = True + return ret + + do_c = do_cont = do_continue + + def do_quit(self, arg): + """Raise Exit outcome when quit command is used in pdb. + + This is a bit of a hack - it would be better if BdbQuit + could be handled, but this would require to wrap the + whole pytest run, and adjust the report etc. + """ + ret = super().do_quit(arg) + + if cls._recursive_debug == 0: + outcomes.exit("Quitting debugger") + + return ret + + do_q = do_quit + do_exit = do_quit + + def setup(self, f, tb): + """Suspend on setup(). + + Needed after do_continue resumed, and entering another + breakpoint again. + """ + ret = super().setup(f, tb) + if not ret and self._continued: + # pdb.setup() returns True if the command wants to exit + # from the interaction: do not suspend capturing then. + if self._pytest_capman: + self._pytest_capman.suspend_global_capture(in_=True) + return ret + + def get_stack(self, f, t): + stack, i = super().get_stack(f, t) + if f is None: + # Find last non-hidden frame. + i = max(0, len(stack) - 1) + while i and stack[i][0].f_locals.get("__tracebackhide__", False): + i -= 1 + return stack, i + + return PytestPdbWrapper + + @classmethod + def _init_pdb(cls, method, *args, **kwargs): + """Initialize PDB debugging, dropping any IO capturing.""" + import _pytest.config + + if cls._pluginmanager is None: + capman: CaptureManager | None = None + else: + capman = cls._pluginmanager.getplugin("capturemanager") + if capman: + capman.suspend(in_=True) + + if cls._config: + tw = _pytest.config.create_terminal_writer(cls._config) + tw.line() + + if cls._recursive_debug == 0: + # Handle header similar to pdb.set_trace in py37+. + header = kwargs.pop("header", None) + if header is not None: + tw.sep(">", header) + else: + capturing = cls._is_capturing(capman) + if capturing == "global": + tw.sep(">", f"PDB {method} (IO-capturing turned off)") + elif capturing: + tw.sep( + ">", + f"PDB {method} (IO-capturing turned off for {capturing})", + ) + else: + tw.sep(">", f"PDB {method}") + + _pdb = cls._import_pdb_cls(capman)(**kwargs) + + if cls._pluginmanager: + cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb) + return _pdb + + @classmethod + def set_trace(cls, *args, **kwargs) -> None: + """Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing.""" + frame = sys._getframe().f_back + _pdb = cls._init_pdb("set_trace", *args, **kwargs) + _pdb.set_trace(frame) + + +class PdbInvoke: + def pytest_exception_interact( + self, node: Node, call: CallInfo[Any], report: BaseReport + ) -> None: + capman = node.config.pluginmanager.getplugin("capturemanager") + if capman: + capman.suspend_global_capture(in_=True) + out, err = capman.read_global_capture() + sys.stdout.write(out) + sys.stdout.write(err) + assert call.excinfo is not None + + if not isinstance(call.excinfo.value, unittest.SkipTest): + _enter_pdb(node, call.excinfo, report) + + def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: + tb = _postmortem_traceback(excinfo) + post_mortem(tb) + + +class PdbTrace: + @hookimpl(wrapper=True) + def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, object, object]: + wrap_pytest_function_for_tracing(pyfuncitem) + return (yield) + + +def wrap_pytest_function_for_tracing(pyfuncitem) -> None: + """Change the Python function object of the given Function item by a + wrapper which actually enters pdb before calling the python function + itself, effectively leaving the user in the pdb prompt in the first + statement of the function.""" + _pdb = pytestPDB._init_pdb("runcall") + testfunction = pyfuncitem.obj + + # we can't just return `partial(pdb.runcall, testfunction)` because (on + # python < 3.7.4) runcall's first param is `func`, which means we'd get + # an exception if one of the kwargs to testfunction was called `func`. + @functools.wraps(testfunction) + def wrapper(*args, **kwargs) -> None: + func = functools.partial(testfunction, *args, **kwargs) + _pdb.runcall(func) + + pyfuncitem.obj = wrapper + + +def maybe_wrap_pytest_function_for_tracing(pyfuncitem) -> None: + """Wrap the given pytestfunct item for tracing support if --trace was given in + the command line.""" + if pyfuncitem.config.getvalue("trace"): + wrap_pytest_function_for_tracing(pyfuncitem) + + +def _enter_pdb( + node: Node, excinfo: ExceptionInfo[BaseException], rep: BaseReport +) -> BaseReport: + # XXX we reuse the TerminalReporter's terminalwriter + # because this seems to avoid some encoding related troubles + # for not completely clear reasons. + tw = node.config.pluginmanager.getplugin("terminalreporter")._tw + tw.line() + + showcapture = node.config.option.showcapture + + for sectionname, content in ( + ("stdout", rep.capstdout), + ("stderr", rep.capstderr), + ("log", rep.caplog), + ): + if showcapture in (sectionname, "all") and content: + tw.sep(">", "captured " + sectionname) + if content[-1:] == "\n": + content = content[:-1] + tw.line(content) + + tw.sep(">", "traceback") + rep.toterminal(tw) + tw.sep(">", "entering PDB") + tb = _postmortem_traceback(excinfo) + rep._pdbshown = True # type: ignore[attr-defined] + post_mortem(tb) + return rep + + +def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType: + from doctest import UnexpectedException + + if isinstance(excinfo.value, UnexpectedException): + # A doctest.UnexpectedException is not useful for post_mortem. + # Use the underlying exception instead: + return excinfo.value.exc_info[2] + elif isinstance(excinfo.value, ConftestImportFailure): + # A config.ConftestImportFailure is not useful for post_mortem. + # Use the underlying exception instead: + assert excinfo.value.cause.__traceback__ is not None + return excinfo.value.cause.__traceback__ + else: + assert excinfo._excinfo is not None + return excinfo._excinfo[2] + + +def post_mortem(t: types.TracebackType) -> None: + p = pytestPDB._init_pdb("post_mortem") + p.reset() + p.interaction(None, t) + if p.quitting: + outcomes.exit("Quitting debugger") diff --git a/.venv/Lib/site-packages/_pytest/deprecated.py b/.venv/Lib/site-packages/_pytest/deprecated.py new file mode 100644 index 0000000..a605c24 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/deprecated.py @@ -0,0 +1,91 @@ +"""Deprecation messages and bits of code used elsewhere in the codebase that +is planned to be removed in the next pytest release. + +Keeping it in a central location makes it easy to track what is deprecated and should +be removed when the time comes. + +All constants defined in this module should be either instances of +:class:`PytestWarning`, or :class:`UnformattedWarning` +in case of warnings which need to format their messages. +""" + +from __future__ import annotations + +from warnings import warn + +from _pytest.warning_types import PytestDeprecationWarning +from _pytest.warning_types import PytestRemovedIn9Warning +from _pytest.warning_types import UnformattedWarning + + +# set of plugins which have been integrated into the core; we use this list to ignore +# them during registration to avoid conflicts +DEPRECATED_EXTERNAL_PLUGINS = { + "pytest_catchlog", + "pytest_capturelog", + "pytest_faulthandler", +} + + +# This can be* removed pytest 8, but it's harmless and common, so no rush to remove. +# * If you're in the future: "could have been". +YIELD_FIXTURE = PytestDeprecationWarning( + "@pytest.yield_fixture is deprecated.\n" + "Use @pytest.fixture instead; they are the same." +) + +# This deprecation is never really meant to be removed. +PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") + + +HOOK_LEGACY_PATH_ARG = UnformattedWarning( + PytestRemovedIn9Warning, + "The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n" + "see https://docs.pytest.org/en/latest/deprecations.html" + "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path", +) + +NODE_CTOR_FSPATH_ARG = UnformattedWarning( + PytestRemovedIn9Warning, + "The (fspath: py.path.local) argument to {node_type_name} is deprecated. " + "Please use the (path: pathlib.Path) argument instead.\n" + "See https://docs.pytest.org/en/latest/deprecations.html" + "#fspath-argument-for-node-constructors-replaced-with-pathlib-path", +) + +HOOK_LEGACY_MARKING = UnformattedWarning( + PytestDeprecationWarning, + "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" + "Please use the pytest.hook{type}({hook_opts}) decorator instead\n" + " to configure the hooks.\n" + " See https://docs.pytest.org/en/latest/deprecations.html" + "#configuring-hook-specs-impls-using-markers", +) + +MARKED_FIXTURE = PytestRemovedIn9Warning( + "Marks applied to fixtures have no effect\n" + "See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function" +) + +# You want to make some `__init__` or function "private". +# +# def my_private_function(some, args): +# ... +# +# Do this: +# +# def my_private_function(some, args, *, _ispytest: bool = False): +# check_ispytest(_ispytest) +# ... +# +# Change all internal/allowed calls to +# +# my_private_function(some, args, _ispytest=True) +# +# All other calls will get the default _ispytest=False and trigger +# the warning (possibly error in the future). + + +def check_ispytest(ispytest: bool) -> None: + if not ispytest: + warn(PRIVATE, stacklevel=3) diff --git a/.venv/Lib/site-packages/_pytest/doctest.py b/.venv/Lib/site-packages/_pytest/doctest.py new file mode 100644 index 0000000..384dea9 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/doctest.py @@ -0,0 +1,755 @@ +# mypy: allow-untyped-defs +"""Discover and run doctests in modules and test files.""" + +from __future__ import annotations + +import bdb +from contextlib import contextmanager +import functools +import inspect +import os +from pathlib import Path +import platform +import sys +import traceback +import types +from typing import Any +from typing import Callable +from typing import Generator +from typing import Iterable +from typing import Pattern +from typing import Sequence +from typing import TYPE_CHECKING +import warnings + +from _pytest import outcomes +from _pytest._code.code import ExceptionInfo +from _pytest._code.code import ReprFileLocation +from _pytest._code.code import TerminalRepr +from _pytest._io import TerminalWriter +from _pytest.compat import safe_getattr +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.fixtures import fixture +from _pytest.fixtures import TopRequest +from _pytest.nodes import Collector +from _pytest.nodes import Item +from _pytest.outcomes import OutcomeException +from _pytest.outcomes import skip +from _pytest.pathlib import fnmatch_ex +from _pytest.python import Module +from _pytest.python_api import approx +from _pytest.warning_types import PytestWarning + + +if TYPE_CHECKING: + import doctest + + from typing_extensions import Self + +DOCTEST_REPORT_CHOICE_NONE = "none" +DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" +DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" +DOCTEST_REPORT_CHOICE_UDIFF = "udiff" +DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure" + +DOCTEST_REPORT_CHOICES = ( + DOCTEST_REPORT_CHOICE_NONE, + DOCTEST_REPORT_CHOICE_CDIFF, + DOCTEST_REPORT_CHOICE_NDIFF, + DOCTEST_REPORT_CHOICE_UDIFF, + DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE, +) + +# Lazy definition of runner class +RUNNER_CLASS = None +# Lazy definition of output checker class +CHECKER_CLASS: type[doctest.OutputChecker] | None = None + + +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "doctest_optionflags", + "Option flags for doctests", + type="args", + default=["ELLIPSIS"], + ) + parser.addini( + "doctest_encoding", "Encoding used for doctest files", default="utf-8" + ) + group = parser.getgroup("collect") + group.addoption( + "--doctest-modules", + action="store_true", + default=False, + help="Run doctests in all .py modules", + dest="doctestmodules", + ) + group.addoption( + "--doctest-report", + type=str.lower, + default="udiff", + help="Choose another output format for diffs on doctest failure", + choices=DOCTEST_REPORT_CHOICES, + dest="doctestreport", + ) + group.addoption( + "--doctest-glob", + action="append", + default=[], + metavar="pat", + help="Doctests file matching pattern, default: test*.txt", + dest="doctestglob", + ) + group.addoption( + "--doctest-ignore-import-errors", + action="store_true", + default=False, + help="Ignore doctest collection errors", + dest="doctest_ignore_import_errors", + ) + group.addoption( + "--doctest-continue-on-failure", + action="store_true", + default=False, + help="For a given doctest, continue to run after the first failure", + dest="doctest_continue_on_failure", + ) + + +def pytest_unconfigure() -> None: + global RUNNER_CLASS + + RUNNER_CLASS = None + + +def pytest_collect_file( + file_path: Path, + parent: Collector, +) -> DoctestModule | DoctestTextfile | None: + config = parent.config + if file_path.suffix == ".py": + if config.option.doctestmodules and not any( + (_is_setup_py(file_path), _is_main_py(file_path)) + ): + return DoctestModule.from_parent(parent, path=file_path) + elif _is_doctest(config, file_path, parent): + return DoctestTextfile.from_parent(parent, path=file_path) + return None + + +def _is_setup_py(path: Path) -> bool: + if path.name != "setup.py": + return False + contents = path.read_bytes() + return b"setuptools" in contents or b"distutils" in contents + + +def _is_doctest(config: Config, path: Path, parent: Collector) -> bool: + if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path): + return True + globs = config.getoption("doctestglob") or ["test*.txt"] + return any(fnmatch_ex(glob, path) for glob in globs) + + +def _is_main_py(path: Path) -> bool: + return path.name == "__main__.py" + + +class ReprFailDoctest(TerminalRepr): + def __init__( + self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]] + ) -> None: + self.reprlocation_lines = reprlocation_lines + + def toterminal(self, tw: TerminalWriter) -> None: + for reprlocation, lines in self.reprlocation_lines: + for line in lines: + tw.line(line) + reprlocation.toterminal(tw) + + +class MultipleDoctestFailures(Exception): + def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None: + super().__init__() + self.failures = failures + + +def _init_runner_class() -> type[doctest.DocTestRunner]: + import doctest + + class PytestDoctestRunner(doctest.DebugRunner): + """Runner to collect failures. + + Note that the out variable in this case is a list instead of a + stdout-like object. + """ + + def __init__( + self, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, + optionflags: int = 0, + continue_on_failure: bool = True, + ) -> None: + super().__init__(checker=checker, verbose=verbose, optionflags=optionflags) + self.continue_on_failure = continue_on_failure + + def report_failure( + self, + out, + test: doctest.DocTest, + example: doctest.Example, + got: str, + ) -> None: + failure = doctest.DocTestFailure(test, example, got) + if self.continue_on_failure: + out.append(failure) + else: + raise failure + + def report_unexpected_exception( + self, + out, + test: doctest.DocTest, + example: doctest.Example, + exc_info: tuple[type[BaseException], BaseException, types.TracebackType], + ) -> None: + if isinstance(exc_info[1], OutcomeException): + raise exc_info[1] + if isinstance(exc_info[1], bdb.BdbQuit): + outcomes.exit("Quitting debugger") + failure = doctest.UnexpectedException(test, example, exc_info) + if self.continue_on_failure: + out.append(failure) + else: + raise failure + + return PytestDoctestRunner + + +def _get_runner( + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, + optionflags: int = 0, + continue_on_failure: bool = True, +) -> doctest.DocTestRunner: + # We need this in order to do a lazy import on doctest + global RUNNER_CLASS + if RUNNER_CLASS is None: + RUNNER_CLASS = _init_runner_class() + # Type ignored because the continue_on_failure argument is only defined on + # PytestDoctestRunner, which is lazily defined so can't be used as a type. + return RUNNER_CLASS( # type: ignore + checker=checker, + verbose=verbose, + optionflags=optionflags, + continue_on_failure=continue_on_failure, + ) + + +class DoctestItem(Item): + def __init__( + self, + name: str, + parent: DoctestTextfile | DoctestModule, + runner: doctest.DocTestRunner, + dtest: doctest.DocTest, + ) -> None: + super().__init__(name, parent) + self.runner = runner + self.dtest = dtest + + # Stuff needed for fixture support. + self.obj = None + fm = self.session._fixturemanager + fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None) + self._fixtureinfo = fixtureinfo + self.fixturenames = fixtureinfo.names_closure + self._initrequest() + + @classmethod + def from_parent( # type: ignore[override] + cls, + parent: DoctestTextfile | DoctestModule, + *, + name: str, + runner: doctest.DocTestRunner, + dtest: doctest.DocTest, + ) -> Self: + # incompatible signature due to imposed limits on subclass + """The public named constructor.""" + return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) + + def _initrequest(self) -> None: + self.funcargs: dict[str, object] = {} + self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] + + def setup(self) -> None: + self._request._fillfixtures() + globs = dict(getfixture=self._request.getfixturevalue) + for name, value in self._request.getfixturevalue("doctest_namespace").items(): + globs[name] = value + self.dtest.globs.update(globs) + + def runtest(self) -> None: + _check_all_skipped(self.dtest) + self._disable_output_capturing_for_darwin() + failures: list[doctest.DocTestFailure] = [] + # Type ignored because we change the type of `out` from what + # doctest expects. + self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] + if failures: + raise MultipleDoctestFailures(failures) + + def _disable_output_capturing_for_darwin(self) -> None: + """Disable output capturing. Otherwise, stdout is lost to doctest (#985).""" + if platform.system() != "Darwin": + return + capman = self.config.pluginmanager.getplugin("capturemanager") + if capman: + capman.suspend_global_capture(in_=True) + out, err = capman.read_global_capture() + sys.stdout.write(out) + sys.stderr.write(err) + + # TODO: Type ignored -- breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, + excinfo: ExceptionInfo[BaseException], + ) -> str | TerminalRepr: + import doctest + + failures: ( + Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None + ) = None + if isinstance( + excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) + ): + failures = [excinfo.value] + elif isinstance(excinfo.value, MultipleDoctestFailures): + failures = excinfo.value.failures + + if failures is None: + return super().repr_failure(excinfo) + + reprlocation_lines = [] + for failure in failures: + example = failure.example + test = failure.test + filename = test.filename + if test.lineno is None: + lineno = None + else: + lineno = test.lineno + example.lineno + 1 + message = type(failure).__name__ + # TODO: ReprFileLocation doesn't expect a None lineno. + reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] + checker = _get_checker() + report_choice = _get_report_choice(self.config.getoption("doctestreport")) + if lineno is not None: + assert failure.test.docstring is not None + lines = failure.test.docstring.splitlines(False) + # add line numbers to the left of the error message + assert test.lineno is not None + lines = [ + "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines) + ] + # trim docstring error lines to 10 + lines = lines[max(example.lineno - 9, 0) : example.lineno + 1] + else: + lines = [ + "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example" + ] + indent = ">>>" + for line in example.source.splitlines(): + lines.append(f"??? {indent} {line}") + indent = "..." + if isinstance(failure, doctest.DocTestFailure): + lines += checker.output_difference( + example, failure.got, report_choice + ).split("\n") + else: + inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info) + lines += [f"UNEXPECTED EXCEPTION: {inner_excinfo.value!r}"] + lines += [ + x.strip("\n") for x in traceback.format_exception(*failure.exc_info) + ] + reprlocation_lines.append((reprlocation, lines)) + return ReprFailDoctest(reprlocation_lines) + + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: + return self.path, self.dtest.lineno, f"[doctest] {self.name}" + + +def _get_flag_lookup() -> dict[str, int]: + import doctest + + return dict( + DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1, + DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE, + NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE, + ELLIPSIS=doctest.ELLIPSIS, + IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL, + COMPARISON_FLAGS=doctest.COMPARISON_FLAGS, + ALLOW_UNICODE=_get_allow_unicode_flag(), + ALLOW_BYTES=_get_allow_bytes_flag(), + NUMBER=_get_number_flag(), + ) + + +def get_optionflags(config: Config) -> int: + optionflags_str = config.getini("doctest_optionflags") + flag_lookup_table = _get_flag_lookup() + flag_acc = 0 + for flag in optionflags_str: + flag_acc |= flag_lookup_table[flag] + return flag_acc + + +def _get_continue_on_failure(config: Config) -> bool: + continue_on_failure: bool = config.getvalue("doctest_continue_on_failure") + if continue_on_failure: + # We need to turn off this if we use pdb since we should stop at + # the first failure. + if config.getvalue("usepdb"): + continue_on_failure = False + return continue_on_failure + + +class DoctestTextfile(Module): + obj = None + + def collect(self) -> Iterable[DoctestItem]: + import doctest + + # Inspired by doctest.testfile; ideally we would use it directly, + # but it doesn't support passing a custom checker. + encoding = self.config.getini("doctest_encoding") + text = self.path.read_text(encoding) + filename = str(self.path) + name = self.path.name + globs = {"__name__": "__main__"} + + optionflags = get_optionflags(self.config) + + runner = _get_runner( + verbose=False, + optionflags=optionflags, + checker=_get_checker(), + continue_on_failure=_get_continue_on_failure(self.config), + ) + + parser = doctest.DocTestParser() + test = parser.get_doctest(text, globs, name, filename, 0) + if test.examples: + yield DoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) + + +def _check_all_skipped(test: doctest.DocTest) -> None: + """Raise pytest.skip() if all examples in the given DocTest have the SKIP + option set.""" + import doctest + + all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) + if all_skipped: + skip("all tests skipped by +SKIP option") + + +def _is_mocked(obj: object) -> bool: + """Return if an object is possibly a mock object by checking the + existence of a highly improbable attribute.""" + return ( + safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) + is not None + ) + + +@contextmanager +def _patch_unwrap_mock_aware() -> Generator[None]: + """Context manager which replaces ``inspect.unwrap`` with a version + that's aware of mock objects and doesn't recurse into them.""" + real_unwrap = inspect.unwrap + + def _mock_aware_unwrap( + func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None + ) -> Any: + try: + if stop is None or stop is _is_mocked: + return real_unwrap(func, stop=_is_mocked) + _stop = stop + return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) + except Exception as e: + warnings.warn( + f"Got {e!r} when unwrapping {func!r}. This is usually caused " + "by a violation of Python's object protocol; see e.g. " + "https://github.com/pytest-dev/pytest/issues/5080", + PytestWarning, + ) + raise + + inspect.unwrap = _mock_aware_unwrap + try: + yield + finally: + inspect.unwrap = real_unwrap + + +class DoctestModule(Module): + def collect(self) -> Iterable[DoctestItem]: + import doctest + + class MockAwareDocTestFinder(doctest.DocTestFinder): + py_ver_info_minor = sys.version_info[:2] + is_find_lineno_broken = ( + py_ver_info_minor < (3, 11) + or (py_ver_info_minor == (3, 11) and sys.version_info.micro < 9) + or (py_ver_info_minor == (3, 12) and sys.version_info.micro < 3) + ) + if is_find_lineno_broken: + + def _find_lineno(self, obj, source_lines): + """On older Pythons, doctest code does not take into account + `@property`. https://github.com/python/cpython/issues/61648 + + Moreover, wrapped Doctests need to be unwrapped so the correct + line number is returned. #8796 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) + + if hasattr(obj, "__wrapped__"): + # Get the main obj in case of it being wrapped + obj = inspect.unwrap(obj) + + # Type ignored because this is a private function. + return super()._find_lineno( # type:ignore[misc] + obj, + source_lines, + ) + + if sys.version_info < (3, 10): + + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: + """Override _find to work around issue in stdlib. + + https://github.com/pytest-dev/pytest/issues/3456 + https://github.com/python/cpython/issues/69718 + """ + if _is_mocked(obj): + return # pragma: no cover + with _patch_unwrap_mock_aware(): + # Type ignored because this is a private function. + super()._find( # type:ignore[misc] + tests, obj, name, module, source_lines, globs, seen + ) + + if sys.version_info < (3, 13): + + def _from_module(self, module, object): + """`cached_property` objects are never considered a part + of the 'current module'. As such they are skipped by doctest. + Here we override `_from_module` to check the underlying + function instead. https://github.com/python/cpython/issues/107995 + """ + if isinstance(object, functools.cached_property): + object = object.func + + # Type ignored because this is a private function. + return super()._from_module(module, object) # type: ignore[misc] + + try: + module = self.obj + except Collector.CollectError: + if self.config.getvalue("doctest_ignore_import_errors"): + skip(f"unable to import module {self.path!r}") + else: + raise + + # While doctests currently don't support fixtures directly, we still + # need to pick up autouse fixtures. + self.session._fixturemanager.parsefactories(self) + + # Uses internal doctest module parsing mechanism. + finder = MockAwareDocTestFinder() + optionflags = get_optionflags(self.config) + runner = _get_runner( + verbose=False, + optionflags=optionflags, + checker=_get_checker(), + continue_on_failure=_get_continue_on_failure(self.config), + ) + + for test in finder.find(module, module.__name__): + if test.examples: # skip empty doctests + yield DoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) + + +def _init_checker_class() -> type[doctest.OutputChecker]: + import doctest + import re + + class LiteralsOutputChecker(doctest.OutputChecker): + # Based on doctest_nose_plugin.py from the nltk project + # (https://github.com/nltk/nltk) and on the "numtest" doctest extension + # by Sebastien Boisgerault (https://github.com/boisgera/numtest). + + _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) + _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) + _number_re = re.compile( + r""" + (?P + (?P + (?P [+-]?\d*)\.(?P\d+) + | + (?P [+-]?\d+)\. + ) + (?: + [Ee] + (?P [+-]?\d+) + )? + | + (?P [+-]?\d+) + (?: + [Ee] + (?P [+-]?\d+) + ) + ) + """, + re.VERBOSE, + ) + + def check_output(self, want: str, got: str, optionflags: int) -> bool: + if super().check_output(want, got, optionflags): + return True + + allow_unicode = optionflags & _get_allow_unicode_flag() + allow_bytes = optionflags & _get_allow_bytes_flag() + allow_number = optionflags & _get_number_flag() + + if not allow_unicode and not allow_bytes and not allow_number: + return False + + def remove_prefixes(regex: Pattern[str], txt: str) -> str: + return re.sub(regex, r"\1\2", txt) + + if allow_unicode: + want = remove_prefixes(self._unicode_literal_re, want) + got = remove_prefixes(self._unicode_literal_re, got) + + if allow_bytes: + want = remove_prefixes(self._bytes_literal_re, want) + got = remove_prefixes(self._bytes_literal_re, got) + + if allow_number: + got = self._remove_unwanted_precision(want, got) + + return super().check_output(want, got, optionflags) + + def _remove_unwanted_precision(self, want: str, got: str) -> str: + wants = list(self._number_re.finditer(want)) + gots = list(self._number_re.finditer(got)) + if len(wants) != len(gots): + return got + offset = 0 + for w, g in zip(wants, gots): + fraction: str | None = w.group("fraction") + exponent: str | None = w.group("exponent1") + if exponent is None: + exponent = w.group("exponent2") + precision = 0 if fraction is None else len(fraction) + if exponent is not None: + precision -= int(exponent) + if float(w.group()) == approx(float(g.group()), abs=10**-precision): + # They're close enough. Replace the text we actually + # got with the text we want, so that it will match when we + # check the string literally. + got = ( + got[: g.start() + offset] + w.group() + got[g.end() + offset :] + ) + offset += w.end() - w.start() - (g.end() - g.start()) + return got + + return LiteralsOutputChecker + + +def _get_checker() -> doctest.OutputChecker: + """Return a doctest.OutputChecker subclass that supports some + additional options: + + * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' + prefixes (respectively) in string literals. Useful when the same + doctest should run in Python 2 and Python 3. + + * NUMBER to ignore floating-point differences smaller than the + precision of the literal number in the doctest. + + An inner class is used to avoid importing "doctest" at the module + level. + """ + global CHECKER_CLASS + if CHECKER_CLASS is None: + CHECKER_CLASS = _init_checker_class() + return CHECKER_CLASS() + + +def _get_allow_unicode_flag() -> int: + """Register and return the ALLOW_UNICODE flag.""" + import doctest + + return doctest.register_optionflag("ALLOW_UNICODE") + + +def _get_allow_bytes_flag() -> int: + """Register and return the ALLOW_BYTES flag.""" + import doctest + + return doctest.register_optionflag("ALLOW_BYTES") + + +def _get_number_flag() -> int: + """Register and return the NUMBER flag.""" + import doctest + + return doctest.register_optionflag("NUMBER") + + +def _get_report_choice(key: str) -> int: + """Return the actual `doctest` module flag value. + + We want to do it as late as possible to avoid importing `doctest` and all + its dependencies when parsing options, as it adds overhead and breaks tests. + """ + import doctest + + return { + DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF, + DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF, + DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF, + DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE, + DOCTEST_REPORT_CHOICE_NONE: 0, + }[key] + + +@fixture(scope="session") +def doctest_namespace() -> dict[str, Any]: + """Fixture that returns a :py:class:`dict` that will be injected into the + namespace of doctests. + + Usually this fixture is used in conjunction with another ``autouse`` fixture: + + .. code-block:: python + + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace["np"] = numpy + + For more details: :ref:`doctest_namespace`. + """ + return dict() diff --git a/.venv/Lib/site-packages/_pytest/faulthandler.py b/.venv/Lib/site-packages/_pytest/faulthandler.py new file mode 100644 index 0000000..d16aea1 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/faulthandler.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import os +import sys +from typing import Generator + +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.nodes import Item +from _pytest.stash import StashKey +import pytest + + +fault_handler_original_stderr_fd_key = StashKey[int]() +fault_handler_stderr_fd_key = StashKey[int]() + + +def pytest_addoption(parser: Parser) -> None: + help = ( + "Dump the traceback of all threads if a test takes " + "more than TIMEOUT seconds to finish" + ) + parser.addini("faulthandler_timeout", help, default=0.0) + + +def pytest_configure(config: Config) -> None: + import faulthandler + + # at teardown we want to restore the original faulthandler fileno + # but faulthandler has no api to return the original fileno + # so here we stash the stderr fileno to be used at teardown + # sys.stderr and sys.__stderr__ may be closed or patched during the session + # so we can't rely on their values being good at that point (#11572). + stderr_fileno = get_stderr_fileno() + if faulthandler.is_enabled(): + config.stash[fault_handler_original_stderr_fd_key] = stderr_fileno + config.stash[fault_handler_stderr_fd_key] = os.dup(stderr_fileno) + faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key]) + + +def pytest_unconfigure(config: Config) -> None: + import faulthandler + + faulthandler.disable() + # Close the dup file installed during pytest_configure. + if fault_handler_stderr_fd_key in config.stash: + os.close(config.stash[fault_handler_stderr_fd_key]) + del config.stash[fault_handler_stderr_fd_key] + # Re-enable the faulthandler if it was originally enabled. + if fault_handler_original_stderr_fd_key in config.stash: + faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key]) + del config.stash[fault_handler_original_stderr_fd_key] + + +def get_stderr_fileno() -> int: + try: + fileno = sys.stderr.fileno() + # The Twisted Logger will return an invalid file descriptor since it is not backed + # by an FD. So, let's also forward this to the same code path as with pytest-xdist. + if fileno == -1: + raise AttributeError() + return fileno + except (AttributeError, ValueError): + # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. + # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors + # This is potentially dangerous, but the best we can do. + assert sys.__stderr__ is not None + return sys.__stderr__.fileno() + + +def get_timeout_config_value(config: Config) -> float: + return float(config.getini("faulthandler_timeout") or 0.0) + + +@pytest.hookimpl(wrapper=True, trylast=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: + timeout = get_timeout_config_value(item.config) + if timeout > 0: + import faulthandler + + stderr = item.config.stash[fault_handler_stderr_fd_key] + faulthandler.dump_traceback_later(timeout, file=stderr) + try: + return (yield) + finally: + faulthandler.cancel_dump_traceback_later() + else: + return (yield) + + +@pytest.hookimpl(tryfirst=True) +def pytest_enter_pdb() -> None: + """Cancel any traceback dumping due to timeout before entering pdb.""" + import faulthandler + + faulthandler.cancel_dump_traceback_later() + + +@pytest.hookimpl(tryfirst=True) +def pytest_exception_interact() -> None: + """Cancel any traceback dumping due to an interactive exception being + raised.""" + import faulthandler + + faulthandler.cancel_dump_traceback_later() diff --git a/.venv/Lib/site-packages/_pytest/fixtures.py b/.venv/Lib/site-packages/_pytest/fixtures.py new file mode 100644 index 0000000..6b882fa --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/fixtures.py @@ -0,0 +1,1932 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import abc +from collections import defaultdict +from collections import deque +import dataclasses +import functools +import inspect +import os +from pathlib import Path +import sys +import types +from typing import AbstractSet +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import Final +from typing import final +from typing import Generator +from typing import Generic +from typing import Iterable +from typing import Iterator +from typing import Mapping +from typing import MutableMapping +from typing import NoReturn +from typing import Optional +from typing import OrderedDict +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union +import warnings + +import _pytest +from _pytest import nodes +from _pytest._code import getfslineno +from _pytest._code import Source +from _pytest._code.code import FormattedExcinfo +from _pytest._code.code import TerminalRepr +from _pytest._io import TerminalWriter +from _pytest.compat import _PytestWrapper +from _pytest.compat import assert_never +from _pytest.compat import get_real_func +from _pytest.compat import get_real_method +from _pytest.compat import getfuncargnames +from _pytest.compat import getimfunc +from _pytest.compat import getlocation +from _pytest.compat import is_generator +from _pytest.compat import NOTSET +from _pytest.compat import NotSetType +from _pytest.compat import safe_getattr +from _pytest.compat import safe_isclass +from _pytest.config import _PluggyPlugin +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest +from _pytest.deprecated import MARKED_FIXTURE +from _pytest.deprecated import YIELD_FIXTURE +from _pytest.main import Session +from _pytest.mark import Mark +from _pytest.mark import ParameterSet +from _pytest.mark.structures import MarkDecorator +from _pytest.outcomes import fail +from _pytest.outcomes import skip +from _pytest.outcomes import TEST_OUTCOME +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath +from _pytest.scope import _ScopeName +from _pytest.scope import HIGH_SCOPES +from _pytest.scope import Scope + + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + + +if TYPE_CHECKING: + from _pytest.python import CallSpec2 + from _pytest.python import Function + from _pytest.python import Metafunc + + +# The value of the fixture -- return/yield of the fixture function (type variable). +FixtureValue = TypeVar("FixtureValue") +# The type of the fixture function (type variable). +FixtureFunction = TypeVar("FixtureFunction", bound=Callable[..., object]) +# The type of a fixture function (type alias generic in fixture value). +_FixtureFunc = Union[ + Callable[..., FixtureValue], Callable[..., Generator[FixtureValue, None, None]] +] +# The type of FixtureDef.cached_result (type alias generic in fixture value). +_FixtureCachedResult = Union[ + Tuple[ + # The result. + FixtureValue, + # Cache key. + object, + None, + ], + Tuple[ + None, + # Cache key. + object, + # The exception and the original traceback. + Tuple[BaseException, Optional[types.TracebackType]], + ], +] + + +@dataclasses.dataclass(frozen=True) +class PseudoFixtureDef(Generic[FixtureValue]): + cached_result: _FixtureCachedResult[FixtureValue] + _scope: Scope + + +def pytest_sessionstart(session: Session) -> None: + session._fixturemanager = FixtureManager(session) + + +def get_scope_package( + node: nodes.Item, + fixturedef: FixtureDef[object], +) -> nodes.Node | None: + from _pytest.python import Package + + for parent in node.iter_parents(): + if isinstance(parent, Package) and parent.nodeid == fixturedef.baseid: + return parent + return node.session + + +def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None: + import _pytest.python + + if scope is Scope.Function: + # Type ignored because this is actually safe, see: + # https://github.com/python/mypy/issues/4717 + return node.getparent(nodes.Item) # type: ignore[type-abstract] + elif scope is Scope.Class: + return node.getparent(_pytest.python.Class) + elif scope is Scope.Module: + return node.getparent(_pytest.python.Module) + elif scope is Scope.Package: + return node.getparent(_pytest.python.Package) + elif scope is Scope.Session: + return node.getparent(_pytest.main.Session) + else: + assert_never(scope) + + +def getfixturemarker(obj: object) -> FixtureFunctionMarker | None: + """Return fixturemarker or None if it doesn't exist or raised + exceptions.""" + return cast( + Optional[FixtureFunctionMarker], + safe_getattr(obj, "_pytestfixturefunction", None), + ) + + +# Algorithm for sorting on a per-parametrized resource setup basis. +# It is called for Session scope first and performs sorting +# down to the lower scopes such as to minimize number of "high scope" +# setups and teardowns. + + +@dataclasses.dataclass(frozen=True) +class FixtureArgKey: + argname: str + param_index: int + scoped_item_path: Path | None + item_cls: type | None + + +_V = TypeVar("_V") +OrderedSet = Dict[_V, None] + + +def get_parametrized_fixture_argkeys( + item: nodes.Item, scope: Scope +) -> Iterator[FixtureArgKey]: + """Return list of keys for all parametrized arguments which match + the specified scope.""" + assert scope is not Scope.Function + + try: + callspec: CallSpec2 = item.callspec # type: ignore[attr-defined] + except AttributeError: + return + + item_cls = None + if scope is Scope.Session: + scoped_item_path = None + elif scope is Scope.Package: + # Package key = module's directory. + scoped_item_path = item.path.parent + elif scope is Scope.Module: + scoped_item_path = item.path + elif scope is Scope.Class: + scoped_item_path = item.path + item_cls = item.cls # type: ignore[attr-defined] + else: + assert_never(scope) + + for argname in callspec.indices: + if callspec._arg2scope[argname] != scope: + continue + param_index = callspec.indices[argname] + yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls) + + +def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]: + argkeys_by_item: dict[Scope, dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {} + items_by_argkey: dict[ + Scope, dict[FixtureArgKey, OrderedDict[nodes.Item, None]] + ] = {} + for scope in HIGH_SCOPES: + scoped_argkeys_by_item = argkeys_by_item[scope] = {} + scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(OrderedDict) + for item in items: + argkeys = dict.fromkeys(get_parametrized_fixture_argkeys(item, scope)) + if argkeys: + scoped_argkeys_by_item[item] = argkeys + for argkey in argkeys: + scoped_items_by_argkey[argkey][item] = None + + items_set = dict.fromkeys(items) + return list( + reorder_items_atscope( + items_set, argkeys_by_item, items_by_argkey, Scope.Session + ) + ) + + +def reorder_items_atscope( + items: OrderedSet[nodes.Item], + argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[FixtureArgKey]]], + items_by_argkey: Mapping[ + Scope, Mapping[FixtureArgKey, OrderedDict[nodes.Item, None]] + ], + scope: Scope, +) -> OrderedSet[nodes.Item]: + if scope is Scope.Function or len(items) < 3: + return items + + scoped_items_by_argkey = items_by_argkey[scope] + scoped_argkeys_by_item = argkeys_by_item[scope] + + ignore: set[FixtureArgKey] = set() + items_deque = deque(items) + items_done: OrderedSet[nodes.Item] = {} + while items_deque: + no_argkey_items: OrderedSet[nodes.Item] = {} + slicing_argkey = None + while items_deque: + item = items_deque.popleft() + if item in items_done or item in no_argkey_items: + continue + argkeys = dict.fromkeys( + k for k in scoped_argkeys_by_item.get(item, ()) if k not in ignore + ) + if not argkeys: + no_argkey_items[item] = None + else: + slicing_argkey, _ = argkeys.popitem() + # We don't have to remove relevant items from later in the + # deque because they'll just be ignored. + matching_items = [ + i for i in scoped_items_by_argkey[slicing_argkey] if i in items + ] + for i in reversed(matching_items): + items_deque.appendleft(i) + # Fix items_by_argkey order. + for other_scope in HIGH_SCOPES: + other_scoped_items_by_argkey = items_by_argkey[other_scope] + for argkey in argkeys_by_item[other_scope].get(i, ()): + other_scoped_items_by_argkey[argkey][i] = None + other_scoped_items_by_argkey[argkey].move_to_end( + i, last=False + ) + break + if no_argkey_items: + reordered_no_argkey_items = reorder_items_atscope( + no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower() + ) + items_done.update(reordered_no_argkey_items) + if slicing_argkey is not None: + ignore.add(slicing_argkey) + return items_done + + +@dataclasses.dataclass(frozen=True) +class FuncFixtureInfo: + """Fixture-related information for a fixture-requesting item (e.g. test + function). + + This is used to examine the fixtures which an item requests statically + (known during collection). This includes autouse fixtures, fixtures + requested by the `usefixtures` marker, fixtures requested in the function + parameters, and the transitive closure of these. + + An item may also request fixtures dynamically (using `request.getfixturevalue`); + these are not reflected here. + """ + + __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") + + # Fixture names that the item requests directly by function parameters. + argnames: tuple[str, ...] + # Fixture names that the item immediately requires. These include + # argnames + fixture names specified via usefixtures and via autouse=True in + # fixture definitions. + initialnames: tuple[str, ...] + # The transitive closure of the fixture names that the item requires. + # Note: can't include dynamic dependencies (`request.getfixturevalue` calls). + names_closure: list[str] + # A map from a fixture name in the transitive closure to the FixtureDefs + # matching the name which are applicable to this function. + # There may be multiple overriding fixtures with the same name. The + # sequence is ordered from furthest to closes to the function. + name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] + + def prune_dependency_tree(self) -> None: + """Recompute names_closure from initialnames and name2fixturedefs. + + Can only reduce names_closure, which means that the new closure will + always be a subset of the old one. The order is preserved. + + This method is needed because direct parametrization may shadow some + of the fixtures that were included in the originally built dependency + tree. In this way the dependency tree can get pruned, and the closure + of argnames may get reduced. + """ + closure: set[str] = set() + working_set = set(self.initialnames) + while working_set: + argname = working_set.pop() + # Argname may be something not included in the original names_closure, + # in which case we ignore it. This currently happens with pseudo + # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'. + # So they introduce the new dependency 'request' which might have + # been missing in the original tree (closure). + if argname not in closure and argname in self.names_closure: + closure.add(argname) + if argname in self.name2fixturedefs: + working_set.update(self.name2fixturedefs[argname][-1].argnames) + + self.names_closure[:] = sorted(closure, key=self.names_closure.index) + + +class FixtureRequest(abc.ABC): + """The type of the ``request`` fixture. + + A request object gives access to the requesting test context and has a + ``param`` attribute in case the fixture is parametrized. + """ + + def __init__( + self, + pyfuncitem: Function, + fixturename: str | None, + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]], + fixture_defs: dict[str, FixtureDef[Any]], + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + #: Fixture for which this request is being performed. + self.fixturename: Final = fixturename + self._pyfuncitem: Final = pyfuncitem + # The FixtureDefs for each fixture name requested by this item. + # Starts from the statically-known fixturedefs resolved during + # collection. Dynamically requested fixtures (using + # `request.getfixturevalue("foo")`) are added dynamically. + self._arg2fixturedefs: Final = arg2fixturedefs + # The evaluated argnames so far, mapping to the FixtureDef they resolved + # to. + self._fixture_defs: Final = fixture_defs + # Notes on the type of `param`: + # -`request.param` is only defined in parametrized fixtures, and will raise + # AttributeError otherwise. Python typing has no notion of "undefined", so + # this cannot be reflected in the type. + # - Technically `param` is only (possibly) defined on SubRequest, not + # FixtureRequest, but the typing of that is still in flux so this cheats. + # - In the future we might consider using a generic for the param type, but + # for now just using Any. + self.param: Any + + @property + def _fixturemanager(self) -> FixtureManager: + return self._pyfuncitem.session._fixturemanager + + @property + @abc.abstractmethod + def _scope(self) -> Scope: + raise NotImplementedError() + + @property + def scope(self) -> _ScopeName: + """Scope string, one of "function", "class", "module", "package", "session".""" + return self._scope.value + + @abc.abstractmethod + def _check_scope( + self, + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], + requested_scope: Scope, + ) -> None: + raise NotImplementedError() + + @property + def fixturenames(self) -> list[str]: + """Names of all active fixtures in this request.""" + result = list(self._pyfuncitem.fixturenames) + result.extend(set(self._fixture_defs).difference(result)) + return result + + @property + @abc.abstractmethod + def node(self): + """Underlying collection node (depends on current request scope).""" + raise NotImplementedError() + + @property + def config(self) -> Config: + """The pytest config object associated with this request.""" + return self._pyfuncitem.config + + @property + def function(self): + """Test function object if the request has a per-function scope.""" + if self.scope != "function": + raise AttributeError( + f"function not available in {self.scope}-scoped context" + ) + return self._pyfuncitem.obj + + @property + def cls(self): + """Class (can be None) where the test function was collected.""" + if self.scope not in ("class", "function"): + raise AttributeError(f"cls not available in {self.scope}-scoped context") + clscol = self._pyfuncitem.getparent(_pytest.python.Class) + if clscol: + return clscol.obj + + @property + def instance(self): + """Instance (can be None) on which test function was collected.""" + if self.scope != "function": + return None + return getattr(self._pyfuncitem, "instance", None) + + @property + def module(self): + """Python module object where the test function was collected.""" + if self.scope not in ("function", "class", "module"): + raise AttributeError(f"module not available in {self.scope}-scoped context") + mod = self._pyfuncitem.getparent(_pytest.python.Module) + assert mod is not None + return mod.obj + + @property + def path(self) -> Path: + """Path where the test function was collected.""" + if self.scope not in ("function", "class", "module", "package"): + raise AttributeError(f"path not available in {self.scope}-scoped context") + return self._pyfuncitem.path + + @property + def keywords(self) -> MutableMapping[str, Any]: + """Keywords/markers dictionary for the underlying node.""" + node: nodes.Node = self.node + return node.keywords + + @property + def session(self) -> Session: + """Pytest session object.""" + return self._pyfuncitem.session + + @abc.abstractmethod + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + """Add finalizer/teardown function to be called without arguments after + the last test within the requesting test context finished execution.""" + raise NotImplementedError() + + def applymarker(self, marker: str | MarkDecorator) -> None: + """Apply a marker to a single test function invocation. + + This method is useful if you don't want to have a keyword/marker + on all function invocations. + + :param marker: + An object created by a call to ``pytest.mark.NAME(...)``. + """ + self.node.add_marker(marker) + + def raiseerror(self, msg: str | None) -> NoReturn: + """Raise a FixtureLookupError exception. + + :param msg: + An optional custom error message. + """ + raise FixtureLookupError(None, self, msg) + + def getfixturevalue(self, argname: str) -> Any: + """Dynamically run a named fixture function. + + Declaring fixtures via function argument is recommended where possible. + But if you can only decide whether to use another fixture at test + setup time, you may use this function to retrieve it inside a fixture + or test function body. + + This method can be used during the test setup phase or the test run + phase, but during the test teardown phase a fixture's value may not + be available. + + :param argname: + The fixture name. + :raises pytest.FixtureLookupError: + If the given fixture could not be found. + """ + # Note that in addition to the use case described in the docstring, + # getfixturevalue() is also called by pytest itself during item and fixture + # setup to evaluate the fixtures that are requested statically + # (using function parameters, autouse, etc). + + fixturedef = self._get_active_fixturedef(argname) + assert fixturedef.cached_result is not None, ( + f'The fixture value for "{argname}" is not available. ' + "This can happen when the fixture has already been torn down." + ) + return fixturedef.cached_result[0] + + def _iter_chain(self) -> Iterator[SubRequest]: + """Yield all SubRequests in the chain, from self up. + + Note: does *not* yield the TopRequest. + """ + current = self + while isinstance(current, SubRequest): + yield current + current = current._parent_request + + def _get_active_fixturedef( + self, argname: str + ) -> FixtureDef[object] | PseudoFixtureDef[object]: + if argname == "request": + cached_result = (self, [0], None) + return PseudoFixtureDef(cached_result, Scope.Function) + + # If we already finished computing a fixture by this name in this item, + # return it. + fixturedef = self._fixture_defs.get(argname) + if fixturedef is not None: + self._check_scope(fixturedef, fixturedef._scope) + return fixturedef + + # Find the appropriate fixturedef. + fixturedefs = self._arg2fixturedefs.get(argname, None) + if fixturedefs is None: + # We arrive here because of a dynamic call to + # getfixturevalue(argname) which was naturally + # not known at parsing/collection time. + fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem) + if fixturedefs is not None: + self._arg2fixturedefs[argname] = fixturedefs + # No fixtures defined with this name. + if fixturedefs is None: + raise FixtureLookupError(argname, self) + # The are no fixtures with this name applicable for the function. + if not fixturedefs: + raise FixtureLookupError(argname, self) + # A fixture may override another fixture with the same name, e.g. a + # fixture in a module can override a fixture in a conftest, a fixture in + # a class can override a fixture in the module, and so on. + # An overriding fixture can request its own name (possibly indirectly); + # in this case it gets the value of the fixture it overrides, one level + # up. + # Check how many `argname`s deep we are, and take the next one. + # `fixturedefs` is sorted from furthest to closest, so use negative + # indexing to go in reverse. + index = -1 + for request in self._iter_chain(): + if request.fixturename == argname: + index -= 1 + # If already consumed all of the available levels, fail. + if -index > len(fixturedefs): + raise FixtureLookupError(argname, self) + fixturedef = fixturedefs[index] + + # Prepare a SubRequest object for calling the fixture. + try: + callspec = self._pyfuncitem.callspec + except AttributeError: + callspec = None + if callspec is not None and argname in callspec.params: + param = callspec.params[argname] + param_index = callspec.indices[argname] + # The parametrize invocation scope overrides the fixture's scope. + scope = callspec._arg2scope[argname] + else: + param = NOTSET + param_index = 0 + scope = fixturedef._scope + self._check_fixturedef_without_param(fixturedef) + self._check_scope(fixturedef, scope) + subrequest = SubRequest( + self, scope, param, param_index, fixturedef, _ispytest=True + ) + + # Make sure the fixture value is cached, running it if it isn't + fixturedef.execute(request=subrequest) + + self._fixture_defs[argname] = fixturedef + return fixturedef + + def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None: + """Check that this request is allowed to execute this fixturedef without + a param.""" + funcitem = self._pyfuncitem + has_params = fixturedef.params is not None + fixtures_not_supported = getattr(funcitem, "nofuncargs", False) + if has_params and fixtures_not_supported: + msg = ( + f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n" + f"Node id: {funcitem.nodeid}\n" + f"Function type: {type(funcitem).__name__}" + ) + fail(msg, pytrace=False) + if has_params: + frame = inspect.stack()[3] + frameinfo = inspect.getframeinfo(frame[0]) + source_path = absolutepath(frameinfo.filename) + source_lineno = frameinfo.lineno + try: + source_path_str = str(source_path.relative_to(funcitem.config.rootpath)) + except ValueError: + source_path_str = str(source_path) + location = getlocation(fixturedef.func, funcitem.config.rootpath) + msg = ( + "The requested fixture has no parameter defined for test:\n" + f" {funcitem.nodeid}\n\n" + f"Requested fixture '{fixturedef.argname}' defined in:\n" + f"{location}\n\n" + f"Requested here:\n" + f"{source_path_str}:{source_lineno}" + ) + fail(msg, pytrace=False) + + def _get_fixturestack(self) -> list[FixtureDef[Any]]: + values = [request._fixturedef for request in self._iter_chain()] + values.reverse() + return values + + +@final +class TopRequest(FixtureRequest): + """The type of the ``request`` fixture in a test function.""" + + def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None: + super().__init__( + fixturename=None, + pyfuncitem=pyfuncitem, + arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs.copy(), + fixture_defs={}, + _ispytest=_ispytest, + ) + + @property + def _scope(self) -> Scope: + return Scope.Function + + def _check_scope( + self, + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], + requested_scope: Scope, + ) -> None: + # TopRequest always has function scope so always valid. + pass + + @property + def node(self): + return self._pyfuncitem + + def __repr__(self) -> str: + return f"" + + def _fillfixtures(self) -> None: + item = self._pyfuncitem + for argname in item.fixturenames: + if argname not in item.funcargs: + item.funcargs[argname] = self.getfixturevalue(argname) + + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + self.node.addfinalizer(finalizer) + + +@final +class SubRequest(FixtureRequest): + """The type of the ``request`` fixture in a fixture function requested + (transitively) by a test function.""" + + def __init__( + self, + request: FixtureRequest, + scope: Scope, + param: Any, + param_index: int, + fixturedef: FixtureDef[object], + *, + _ispytest: bool = False, + ) -> None: + super().__init__( + pyfuncitem=request._pyfuncitem, + fixturename=fixturedef.argname, + fixture_defs=request._fixture_defs, + arg2fixturedefs=request._arg2fixturedefs, + _ispytest=_ispytest, + ) + self._parent_request: Final[FixtureRequest] = request + self._scope_field: Final = scope + self._fixturedef: Final[FixtureDef[object]] = fixturedef + if param is not NOTSET: + self.param = param + self.param_index: Final = param_index + + def __repr__(self) -> str: + return f"" + + @property + def _scope(self) -> Scope: + return self._scope_field + + @property + def node(self): + scope = self._scope + if scope is Scope.Function: + # This might also be a non-function Item despite its attribute name. + node: nodes.Node | None = self._pyfuncitem + elif scope is Scope.Package: + node = get_scope_package(self._pyfuncitem, self._fixturedef) + else: + node = get_scope_node(self._pyfuncitem, scope) + if node is None and scope is Scope.Class: + # Fallback to function item itself. + node = self._pyfuncitem + assert node, f'Could not obtain a node for scope "{scope}" for function {self._pyfuncitem!r}' + return node + + def _check_scope( + self, + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], + requested_scope: Scope, + ) -> None: + if isinstance(requested_fixturedef, PseudoFixtureDef): + return + if self._scope > requested_scope: + # Try to report something helpful. + argname = requested_fixturedef.argname + fixture_stack = "\n".join( + self._format_fixturedef_line(fixturedef) + for fixturedef in self._get_fixturestack() + ) + requested_fixture = self._format_fixturedef_line(requested_fixturedef) + fail( + f"ScopeMismatch: You tried to access the {requested_scope.value} scoped " + f"fixture {argname} with a {self._scope.value} scoped request object. " + f"Requesting fixture stack:\n{fixture_stack}\n" + f"Requested fixture:\n{requested_fixture}", + pytrace=False, + ) + + def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str: + factory = fixturedef.func + path, lineno = getfslineno(factory) + if isinstance(path, Path): + path = bestrelpath(self._pyfuncitem.session.path, path) + signature = inspect.signature(factory) + return f"{path}:{lineno + 1}: def {factory.__name__}{signature}" + + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + self._fixturedef.addfinalizer(finalizer) + + +@final +class FixtureLookupError(LookupError): + """Could not return a requested fixture (missing or invalid).""" + + def __init__( + self, argname: str | None, request: FixtureRequest, msg: str | None = None + ) -> None: + self.argname = argname + self.request = request + self.fixturestack = request._get_fixturestack() + self.msg = msg + + def formatrepr(self) -> FixtureLookupErrorRepr: + tblines: list[str] = [] + addline = tblines.append + stack = [self.request._pyfuncitem.obj] + stack.extend(map(lambda x: x.func, self.fixturestack)) + msg = self.msg + if msg is not None: + # The last fixture raise an error, let's present + # it at the requesting side. + stack = stack[:-1] + for function in stack: + fspath, lineno = getfslineno(function) + try: + lines, _ = inspect.getsourcelines(get_real_func(function)) + except (OSError, IndexError, TypeError): + error_msg = "file %s, line %s: source code not available" + addline(error_msg % (fspath, lineno + 1)) + else: + addline(f"file {fspath}, line {lineno + 1}") + for i, line in enumerate(lines): + line = line.rstrip() + addline(" " + line) + if line.lstrip().startswith("def"): + break + + if msg is None: + fm = self.request._fixturemanager + available = set() + parent = self.request._pyfuncitem.parent + assert parent is not None + for name, fixturedefs in fm._arg2fixturedefs.items(): + faclist = list(fm._matchfactories(fixturedefs, parent)) + if faclist: + available.add(name) + if self.argname in available: + msg = ( + f" recursive dependency involving fixture '{self.argname}' detected" + ) + else: + msg = f"fixture '{self.argname}' not found" + msg += "\n available fixtures: {}".format(", ".join(sorted(available))) + msg += "\n use 'pytest --fixtures [testpath]' for help on them." + + return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) + + +class FixtureLookupErrorRepr(TerminalRepr): + def __init__( + self, + filename: str | os.PathLike[str], + firstlineno: int, + tblines: Sequence[str], + errorstring: str, + argname: str | None, + ) -> None: + self.tblines = tblines + self.errorstring = errorstring + self.filename = filename + self.firstlineno = firstlineno + self.argname = argname + + def toterminal(self, tw: TerminalWriter) -> None: + # tw.line("FixtureLookupError: %s" %(self.argname), red=True) + for tbline in self.tblines: + tw.line(tbline.rstrip()) + lines = self.errorstring.split("\n") + if lines: + tw.line( + f"{FormattedExcinfo.fail_marker} {lines[0].strip()}", + red=True, + ) + for line in lines[1:]: + tw.line( + f"{FormattedExcinfo.flow_marker} {line.strip()}", + red=True, + ) + tw.line() + tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1)) + + +def call_fixture_func( + fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs +) -> FixtureValue: + if is_generator(fixturefunc): + fixturefunc = cast( + Callable[..., Generator[FixtureValue, None, None]], fixturefunc + ) + generator = fixturefunc(**kwargs) + try: + fixture_result = next(generator) + except StopIteration: + raise ValueError(f"{request.fixturename} did not yield a value") from None + finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator) + request.addfinalizer(finalizer) + else: + fixturefunc = cast(Callable[..., FixtureValue], fixturefunc) + fixture_result = fixturefunc(**kwargs) + return fixture_result + + +def _teardown_yield_fixture(fixturefunc, it) -> None: + """Execute the teardown of a fixture function by advancing the iterator + after the yield and ensure the iteration ends (if not it means there is + more than one yield in the function).""" + try: + next(it) + except StopIteration: + pass + else: + fs, lineno = getfslineno(fixturefunc) + fail( + f"fixture function has more than one 'yield':\n\n" + f"{Source(fixturefunc).indent()}\n" + f"{fs}:{lineno + 1}", + pytrace=False, + ) + + +def _eval_scope_callable( + scope_callable: Callable[[str, Config], _ScopeName], + fixture_name: str, + config: Config, +) -> _ScopeName: + try: + # Type ignored because there is no typing mechanism to specify + # keyword arguments, currently. + result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg] + except Exception as e: + raise TypeError( + f"Error evaluating {scope_callable} while defining fixture '{fixture_name}'.\n" + "Expected a function with the signature (*, fixture_name, config)" + ) from e + if not isinstance(result, str): + fail( + f"Expected {scope_callable} to return a 'str' while defining fixture '{fixture_name}', but it returned:\n" + f"{result!r}", + pytrace=False, + ) + return result + + +@final +class FixtureDef(Generic[FixtureValue]): + """A container for a fixture definition. + + Note: At this time, only explicitly documented fields and methods are + considered public stable API. + """ + + def __init__( + self, + config: Config, + baseid: str | None, + argname: str, + func: _FixtureFunc[FixtureValue], + scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None, + params: Sequence[object] | None, + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + # The "base" node ID for the fixture. + # + # This is a node ID prefix. A fixture is only available to a node (e.g. + # a `Function` item) if the fixture's baseid is a nodeid of a parent of + # node. + # + # For a fixture found in a Collector's object (e.g. a `Module`s module, + # a `Class`'s class), the baseid is the Collector's nodeid. + # + # For a fixture found in a conftest plugin, the baseid is the conftest's + # directory path relative to the rootdir. + # + # For other plugins, the baseid is the empty string (always matches). + self.baseid: Final = baseid or "" + # Whether the fixture was found from a node or a conftest in the + # collection tree. Will be false for fixtures defined in non-conftest + # plugins. + self.has_location: Final = baseid is not None + # The fixture factory function. + self.func: Final = func + # The name by which the fixture may be requested. + self.argname: Final = argname + if scope is None: + scope = Scope.Function + elif callable(scope): + scope = _eval_scope_callable(scope, argname, config) + if isinstance(scope, str): + scope = Scope.from_user( + scope, descr=f"Fixture '{func.__name__}'", where=baseid + ) + self._scope: Final = scope + # If the fixture is directly parametrized, the parameter values. + self.params: Final = params + # If the fixture is directly parametrized, a tuple of explicit IDs to + # assign to the parameter values, or a callable to generate an ID given + # a parameter value. + self.ids: Final = ids + # The names requested by the fixtures. + self.argnames: Final = getfuncargnames(func, name=argname) + # If the fixture was executed, the current value of the fixture. + # Can change if the fixture is executed with different parameters. + self.cached_result: _FixtureCachedResult[FixtureValue] | None = None + self._finalizers: Final[list[Callable[[], object]]] = [] + + @property + def scope(self) -> _ScopeName: + """Scope string, one of "function", "class", "module", "package", "session".""" + return self._scope.value + + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + self._finalizers.append(finalizer) + + def finish(self, request: SubRequest) -> None: + exceptions: list[BaseException] = [] + while self._finalizers: + fin = self._finalizers.pop() + try: + fin() + except BaseException as e: + exceptions.append(e) + node = request.node + node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request) + # Even if finalization fails, we invalidate the cached fixture + # value and remove all finalizers because they may be bound methods + # which will keep instances alive. + self.cached_result = None + self._finalizers.clear() + if len(exceptions) == 1: + raise exceptions[0] + elif len(exceptions) > 1: + msg = f'errors while tearing down fixture "{self.argname}" of {node}' + raise BaseExceptionGroup(msg, exceptions[::-1]) + + def execute(self, request: SubRequest) -> FixtureValue: + """Return the value of this fixture, executing it if not cached.""" + # Ensure that the dependent fixtures requested by this fixture are loaded. + # This needs to be done before checking if we have a cached value, since + # if a dependent fixture has their cache invalidated, e.g. due to + # parametrization, they finalize themselves and fixtures depending on it + # (which will likely include this fixture) setting `self.cached_result = None`. + # See #4871 + requested_fixtures_that_should_finalize_us = [] + for argname in self.argnames: + fixturedef = request._get_active_fixturedef(argname) + # Saves requested fixtures in a list so we later can add our finalizer + # to them, ensuring that if a requested fixture gets torn down we get torn + # down first. This is generally handled by SetupState, but still currently + # needed when this fixture is not parametrized but depends on a parametrized + # fixture. + if not isinstance(fixturedef, PseudoFixtureDef): + requested_fixtures_that_should_finalize_us.append(fixturedef) + + # Check for (and return) cached value/exception. + if self.cached_result is not None: + request_cache_key = self.cache_key(request) + cache_key = self.cached_result[1] + try: + # Attempt to make a normal == check: this might fail for objects + # which do not implement the standard comparison (like numpy arrays -- #6497). + cache_hit = bool(request_cache_key == cache_key) + except (ValueError, RuntimeError): + # If the comparison raises, use 'is' as fallback. + cache_hit = request_cache_key is cache_key + + if cache_hit: + if self.cached_result[2] is not None: + exc, exc_tb = self.cached_result[2] + raise exc.with_traceback(exc_tb) + else: + result = self.cached_result[0] + return result + # We have a previous but differently parametrized fixture instance + # so we need to tear it down before creating a new one. + self.finish(request) + assert self.cached_result is None + + # Add finalizer to requested fixtures we saved previously. + # We make sure to do this after checking for cached value to avoid + # adding our finalizer multiple times. (#12135) + finalizer = functools.partial(self.finish, request=request) + for parent_fixture in requested_fixtures_that_should_finalize_us: + parent_fixture.addfinalizer(finalizer) + + ihook = request.node.ihook + try: + # Setup the fixture, run the code in it, and cache the value + # in self.cached_result + result = ihook.pytest_fixture_setup(fixturedef=self, request=request) + finally: + # schedule our finalizer, even if the setup failed + request.node.addfinalizer(finalizer) + + return result + + def cache_key(self, request: SubRequest) -> object: + return getattr(request, "param", None) + + def __repr__(self) -> str: + return f"" + + +def resolve_fixture_function( + fixturedef: FixtureDef[FixtureValue], request: FixtureRequest +) -> _FixtureFunc[FixtureValue]: + """Get the actual callable that can be called to obtain the fixture + value.""" + fixturefunc = fixturedef.func + # The fixture function needs to be bound to the actual + # request.instance so that code working with "fixturedef" behaves + # as expected. + instance = request.instance + if instance is not None: + # Handle the case where fixture is defined not in a test class, but some other class + # (for example a plugin class with a fixture), see #2270. + if hasattr(fixturefunc, "__self__") and not isinstance( + instance, + fixturefunc.__self__.__class__, + ): + return fixturefunc + fixturefunc = getimfunc(fixturedef.func) + if fixturefunc != fixturedef.func: + fixturefunc = fixturefunc.__get__(instance) + return fixturefunc + + +def pytest_fixture_setup( + fixturedef: FixtureDef[FixtureValue], request: SubRequest +) -> FixtureValue: + """Execution of fixture setup.""" + kwargs = {} + for argname in fixturedef.argnames: + kwargs[argname] = request.getfixturevalue(argname) + + fixturefunc = resolve_fixture_function(fixturedef, request) + my_cache_key = fixturedef.cache_key(request) + try: + result = call_fixture_func(fixturefunc, request, kwargs) + except TEST_OUTCOME as e: + if isinstance(e, skip.Exception): + # The test requested a fixture which caused a skip. + # Don't show the fixture as the skip location, as then the user + # wouldn't know which test skipped. + e._use_item_location = True + fixturedef.cached_result = (None, my_cache_key, (e, e.__traceback__)) + raise + fixturedef.cached_result = (result, my_cache_key, None) + return result + + +def wrap_function_to_error_out_if_called_directly( + function: FixtureFunction, + fixture_marker: FixtureFunctionMarker, +) -> FixtureFunction: + """Wrap the given fixture function so we can raise an error about it being called directly, + instead of used as an argument in a test function.""" + name = fixture_marker.name or function.__name__ + message = ( + f'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n' + "but are created automatically when test functions request them as parameters.\n" + "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n" + "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code." + ) + + @functools.wraps(function) + def result(*args, **kwargs): + fail(message, pytrace=False) + + # Keep reference to the original function in our own custom attribute so we don't unwrap + # further than this point and lose useful wrappings like @mock.patch (#3774). + result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined] + + return cast(FixtureFunction, result) + + +@final +@dataclasses.dataclass(frozen=True) +class FixtureFunctionMarker: + scope: _ScopeName | Callable[[str, Config], _ScopeName] + params: tuple[object, ...] | None + autouse: bool = False + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None + name: str | None = None + + _ispytest: dataclasses.InitVar[bool] = False + + def __post_init__(self, _ispytest: bool) -> None: + check_ispytest(_ispytest) + + def __call__(self, function: FixtureFunction) -> FixtureFunction: + if inspect.isclass(function): + raise ValueError("class fixtures not supported (maybe in the future)") + + if getattr(function, "_pytestfixturefunction", False): + raise ValueError( + f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}" + ) + + if hasattr(function, "pytestmark"): + warnings.warn(MARKED_FIXTURE, stacklevel=2) + + function = wrap_function_to_error_out_if_called_directly(function, self) + + name = self.name or function.__name__ + if name == "request": + location = getlocation(function) + fail( + f"'request' is a reserved word for fixtures, use another name:\n {location}", + pytrace=False, + ) + + # Type ignored because https://github.com/python/mypy/issues/2087. + function._pytestfixturefunction = self # type: ignore[attr-defined] + return function + + +@overload +def fixture( + fixture_function: FixtureFunction, + *, + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., + autouse: bool = ..., + ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., + name: str | None = ..., +) -> FixtureFunction: ... + + +@overload +def fixture( + fixture_function: None = ..., + *, + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., + autouse: bool = ..., + ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., + name: str | None = None, +) -> FixtureFunctionMarker: ... + + +def fixture( + fixture_function: FixtureFunction | None = None, + *, + scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function", + params: Iterable[object] | None = None, + autouse: bool = False, + ids: Sequence[object | None] | Callable[[Any], object | None] | None = None, + name: str | None = None, +) -> FixtureFunctionMarker | FixtureFunction: + """Decorator to mark a fixture factory function. + + This decorator can be used, with or without parameters, to define a + fixture function. + + The name of the fixture function can later be referenced to cause its + invocation ahead of running tests: test modules or classes can use the + ``pytest.mark.usefixtures(fixturename)`` marker. + + Test functions can directly use fixture names as input arguments in which + case the fixture instance returned from the fixture function will be + injected. + + Fixtures can provide their values to test functions using ``return`` or + ``yield`` statements. When using ``yield`` the code block after the + ``yield`` statement is executed as teardown code regardless of the test + outcome, and must yield exactly once. + + :param scope: + The scope for which this fixture is shared; one of ``"function"`` + (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``. + + This parameter may also be a callable which receives ``(fixture_name, config)`` + as parameters, and must return a ``str`` with one of the values mentioned above. + + See :ref:`dynamic scope` in the docs for more information. + + :param params: + An optional list of parameters which will cause multiple invocations + of the fixture function and all of the tests using it. The current + parameter is available in ``request.param``. + + :param autouse: + If True, the fixture func is activated for all tests that can see it. + If False (the default), an explicit reference is needed to activate + the fixture. + + :param ids: + Sequence of ids each corresponding to the params so that they are + part of the test id. If no ids are provided they will be generated + automatically from the params. + + :param name: + The name of the fixture. This defaults to the name of the decorated + function. If a fixture is used in the same module in which it is + defined, the function name of the fixture will be shadowed by the + function arg that requests the fixture; one way to resolve this is to + name the decorated function ``fixture_`` and then use + ``@pytest.fixture(name='')``. + """ + fixture_marker = FixtureFunctionMarker( + scope=scope, + params=tuple(params) if params is not None else None, + autouse=autouse, + ids=None if ids is None else ids if callable(ids) else tuple(ids), + name=name, + _ispytest=True, + ) + + # Direct decoration. + if fixture_function: + return fixture_marker(fixture_function) + + return fixture_marker + + +def yield_fixture( + fixture_function=None, + *args, + scope="function", + params=None, + autouse=False, + ids=None, + name=None, +): + """(Return a) decorator to mark a yield-fixture factory function. + + .. deprecated:: 3.0 + Use :py:func:`pytest.fixture` directly instead. + """ + warnings.warn(YIELD_FIXTURE, stacklevel=2) + return fixture( + fixture_function, + *args, + scope=scope, + params=params, + autouse=autouse, + ids=ids, + name=name, + ) + + +@fixture(scope="session") +def pytestconfig(request: FixtureRequest) -> Config: + """Session-scoped fixture that returns the session's :class:`pytest.Config` + object. + + Example:: + + def test_foo(pytestconfig): + if pytestconfig.get_verbosity() > 0: + ... + + """ + return request.config + + +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "usefixtures", + type="args", + default=[], + help="List of default fixtures to be used with this project", + ) + group = parser.getgroup("general") + group.addoption( + "--fixtures", + "--funcargs", + action="store_true", + dest="showfixtures", + default=False, + help="Show available fixtures, sorted by plugin appearance " + "(fixtures with leading '_' are only shown with '-v')", + ) + group.addoption( + "--fixtures-per-test", + action="store_true", + dest="show_fixtures_per_test", + default=False, + help="Show fixtures per test", + ) + + +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: + if config.option.showfixtures: + showfixtures(config) + return 0 + if config.option.show_fixtures_per_test: + show_fixtures_per_test(config) + return 0 + return None + + +def _get_direct_parametrize_args(node: nodes.Node) -> set[str]: + """Return all direct parametrization arguments of a node, so we don't + mistake them for fixtures. + + Check https://github.com/pytest-dev/pytest/issues/5036. + + These things are done later as well when dealing with parametrization + so this could be improved. + """ + parametrize_argnames: set[str] = set() + for marker in node.iter_markers(name="parametrize"): + if not marker.kwargs.get("indirect", False): + p_argnames, _ = ParameterSet._parse_parametrize_args( + *marker.args, **marker.kwargs + ) + parametrize_argnames.update(p_argnames) + return parametrize_argnames + + +def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]: + """De-duplicate the sequence of names while keeping the original order.""" + # Ideally we would use a set, but it does not preserve insertion order. + return tuple(dict.fromkeys(name for seq in seqs for name in seq)) + + +class FixtureManager: + """pytest fixture definitions and information is stored and managed + from this class. + + During collection fm.parsefactories() is called multiple times to parse + fixture function definitions into FixtureDef objects and internal + data structures. + + During collection of test functions, metafunc-mechanics instantiate + a FuncFixtureInfo object which is cached per node/func-name. + This FuncFixtureInfo object is later retrieved by Function nodes + which themselves offer a fixturenames attribute. + + The FuncFixtureInfo object holds information about fixtures and FixtureDefs + relevant for a particular function. An initial list of fixtures is + assembled like this: + + - ini-defined usefixtures + - autouse-marked fixtures along the collection chain up from the function + - usefixtures markers at module/class/function level + - test function funcargs + + Subsequently the funcfixtureinfo.fixturenames attribute is computed + as the closure of the fixtures needed to setup the initial fixtures, + i.e. fixtures needed by fixture functions themselves are appended + to the fixturenames list. + + Upon the test-setup phases all fixturenames are instantiated, retrieved + by a lookup of their FuncFixtureInfo. + """ + + def __init__(self, session: Session) -> None: + self.session = session + self.config: Config = session.config + # Maps a fixture name (argname) to all of the FixtureDefs in the test + # suite/plugins defined with this name. Populated by parsefactories(). + # TODO: The order of the FixtureDefs list of each arg is significant, + # explain. + self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {} + self._holderobjseen: Final[set[object]] = set() + # A mapping from a nodeid to a list of autouse fixtures it defines. + self._nodeid_autousenames: Final[dict[str, list[str]]] = { + "": self.config.getini("usefixtures"), + } + session.config.pluginmanager.register(self, "funcmanage") + + def getfixtureinfo( + self, + node: nodes.Item, + func: Callable[..., object] | None, + cls: type | None, + ) -> FuncFixtureInfo: + """Calculate the :class:`FuncFixtureInfo` for an item. + + If ``func`` is None, or if the item sets an attribute + ``nofuncargs = True``, then ``func`` is not examined at all. + + :param node: + The item requesting the fixtures. + :param func: + The item's function. + :param cls: + If the function is a method, the method's class. + """ + if func is not None and not getattr(node, "nofuncargs", False): + argnames = getfuncargnames(func, name=node.name, cls=cls) + else: + argnames = () + usefixturesnames = self._getusefixturesnames(node) + autousenames = self._getautousenames(node) + initialnames = deduplicate_names(autousenames, usefixturesnames, argnames) + + direct_parametrize_args = _get_direct_parametrize_args(node) + + names_closure, arg2fixturedefs = self.getfixtureclosure( + parentnode=node, + initialnames=initialnames, + ignore_args=direct_parametrize_args, + ) + + return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs) + + def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> None: + # Fixtures defined in conftest plugins are only visible to within the + # conftest's directory. This is unlike fixtures in non-conftest plugins + # which have global visibility. So for conftests, construct the base + # nodeid from the plugin name (which is the conftest path). + if plugin_name and plugin_name.endswith("conftest.py"): + # Note: we explicitly do *not* use `plugin.__file__` here -- The + # difference is that plugin_name has the correct capitalization on + # case-insensitive systems (Windows) and other normalization issues + # (issue #11816). + conftestpath = absolutepath(plugin_name) + try: + nodeid = str(conftestpath.parent.relative_to(self.config.rootpath)) + except ValueError: + nodeid = "" + if nodeid == ".": + nodeid = "" + if os.sep != nodes.SEP: + nodeid = nodeid.replace(os.sep, nodes.SEP) + else: + nodeid = None + + self.parsefactories(plugin, nodeid) + + def _getautousenames(self, node: nodes.Node) -> Iterator[str]: + """Return the names of autouse fixtures applicable to node.""" + for parentnode in node.listchain(): + basenames = self._nodeid_autousenames.get(parentnode.nodeid) + if basenames: + yield from basenames + + def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]: + """Return the names of usefixtures fixtures applicable to node.""" + for mark in node.iter_markers(name="usefixtures"): + yield from mark.args + + def getfixtureclosure( + self, + parentnode: nodes.Node, + initialnames: tuple[str, ...], + ignore_args: AbstractSet[str], + ) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]: + # Collect the closure of all fixtures, starting with the given + # fixturenames as the initial set. As we have to visit all + # factory definitions anyway, we also return an arg2fixturedefs + # mapping so that the caller can reuse it and does not have + # to re-discover fixturedefs again for each fixturename + # (discovering matching fixtures for a given name/node is expensive). + + fixturenames_closure = list(initialnames) + + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {} + lastlen = -1 + while lastlen != len(fixturenames_closure): + lastlen = len(fixturenames_closure) + for argname in fixturenames_closure: + if argname in ignore_args: + continue + if argname in arg2fixturedefs: + continue + fixturedefs = self.getfixturedefs(argname, parentnode) + if fixturedefs: + arg2fixturedefs[argname] = fixturedefs + for arg in fixturedefs[-1].argnames: + if arg not in fixturenames_closure: + fixturenames_closure.append(arg) + + def sort_by_scope(arg_name: str) -> Scope: + try: + fixturedefs = arg2fixturedefs[arg_name] + except KeyError: + return Scope.Function + else: + return fixturedefs[-1]._scope + + fixturenames_closure.sort(key=sort_by_scope, reverse=True) + return fixturenames_closure, arg2fixturedefs + + def pytest_generate_tests(self, metafunc: Metafunc) -> None: + """Generate new tests based on parametrized fixtures used by the given metafunc""" + + def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: + args, _ = ParameterSet._parse_parametrize_args(*mark.args, **mark.kwargs) + return args + + for argname in metafunc.fixturenames: + # Get the FixtureDefs for the argname. + fixture_defs = metafunc._arg2fixturedefs.get(argname) + if not fixture_defs: + # Will raise FixtureLookupError at setup time if not parametrized somewhere + # else (e.g @pytest.mark.parametrize) + continue + + # If the test itself parametrizes using this argname, give it + # precedence. + if any( + argname in get_parametrize_mark_argnames(mark) + for mark in metafunc.definition.iter_markers("parametrize") + ): + continue + + # In the common case we only look at the fixture def with the + # closest scope (last in the list). But if the fixture overrides + # another fixture, while requesting the super fixture, keep going + # in case the super fixture is parametrized (#1953). + for fixturedef in reversed(fixture_defs): + # Fixture is parametrized, apply it and stop. + if fixturedef.params is not None: + metafunc.parametrize( + argname, + fixturedef.params, + indirect=True, + scope=fixturedef.scope, + ids=fixturedef.ids, + ) + break + + # Not requesting the overridden super fixture, stop. + if argname not in fixturedef.argnames: + break + + # Try next super fixture, if any. + + def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None: + # Separate parametrized setups. + items[:] = reorder_items(items) + + def _register_fixture( + self, + *, + name: str, + func: _FixtureFunc[object], + nodeid: str | None, + scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] = "function", + params: Sequence[object] | None = None, + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, + autouse: bool = False, + ) -> None: + """Register a fixture + + :param name: + The fixture's name. + :param func: + The fixture's implementation function. + :param nodeid: + The visibility of the fixture. The fixture will be available to the + node with this nodeid and its children in the collection tree. + None means that the fixture is visible to the entire collection tree, + e.g. a fixture defined for general use in a plugin. + :param scope: + The fixture's scope. + :param params: + The fixture's parametrization params. + :param ids: + The fixture's IDs. + :param autouse: + Whether this is an autouse fixture. + """ + fixture_def = FixtureDef( + config=self.config, + baseid=nodeid, + argname=name, + func=func, + scope=scope, + params=params, + ids=ids, + _ispytest=True, + ) + + faclist = self._arg2fixturedefs.setdefault(name, []) + if fixture_def.has_location: + faclist.append(fixture_def) + else: + # fixturedefs with no location are at the front + # so this inserts the current fixturedef after the + # existing fixturedefs from external plugins but + # before the fixturedefs provided in conftests. + i = len([f for f in faclist if not f.has_location]) + faclist.insert(i, fixture_def) + if autouse: + self._nodeid_autousenames.setdefault(nodeid or "", []).append(name) + + @overload + def parsefactories( + self, + node_or_obj: nodes.Node, + ) -> None: + raise NotImplementedError() + + @overload + def parsefactories( + self, + node_or_obj: object, + nodeid: str | None, + ) -> None: + raise NotImplementedError() + + def parsefactories( + self, + node_or_obj: nodes.Node | object, + nodeid: str | NotSetType | None = NOTSET, + ) -> None: + """Collect fixtures from a collection node or object. + + Found fixtures are parsed into `FixtureDef`s and saved. + + If `node_or_object` is a collection node (with an underlying Python + object), the node's object is traversed and the node's nodeid is used to + determine the fixtures' visibility. `nodeid` must not be specified in + this case. + + If `node_or_object` is an object (e.g. a plugin), the object is + traversed and the given `nodeid` is used to determine the fixtures' + visibility. `nodeid` must be specified in this case; None and "" mean + total visibility. + """ + if nodeid is not NOTSET: + holderobj = node_or_obj + else: + assert isinstance(node_or_obj, nodes.Node) + holderobj = cast(object, node_or_obj.obj) # type: ignore[attr-defined] + assert isinstance(node_or_obj.nodeid, str) + nodeid = node_or_obj.nodeid + if holderobj in self._holderobjseen: + return + + # Avoid accessing `@property` (and other descriptors) when iterating fixtures. + if not safe_isclass(holderobj) and not isinstance(holderobj, types.ModuleType): + holderobj_tp: object = type(holderobj) + else: + holderobj_tp = holderobj + + self._holderobjseen.add(holderobj) + for name in dir(holderobj): + # The attribute can be an arbitrary descriptor, so the attribute + # access below can raise. safe_getattr() ignores such exceptions. + obj_ub = safe_getattr(holderobj_tp, name, None) + marker = getfixturemarker(obj_ub) + if not isinstance(marker, FixtureFunctionMarker): + # Magic globals with __getattr__ might have got us a wrong + # fixture attribute. + continue + + # OK we know it is a fixture -- now safe to look up on the _instance_. + obj = getattr(holderobj, name) + + if marker.name: + name = marker.name + + # During fixture definition we wrap the original fixture function + # to issue a warning if called directly, so here we unwrap it in + # order to not emit the warning when pytest itself calls the + # fixture function. + func = get_real_method(obj, holderobj) + + self._register_fixture( + name=name, + nodeid=nodeid, + func=func, + scope=marker.scope, + params=marker.params, + ids=marker.ids, + autouse=marker.autouse, + ) + + def getfixturedefs( + self, argname: str, node: nodes.Node + ) -> Sequence[FixtureDef[Any]] | None: + """Get FixtureDefs for a fixture name which are applicable + to a given node. + + Returns None if there are no fixtures at all defined with the given + name. (This is different from the case in which there are fixtures + with the given name, but none applicable to the node. In this case, + an empty result is returned). + + :param argname: Name of the fixture to search for. + :param node: The requesting Node. + """ + try: + fixturedefs = self._arg2fixturedefs[argname] + except KeyError: + return None + return tuple(self._matchfactories(fixturedefs, node)) + + def _matchfactories( + self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node + ) -> Iterator[FixtureDef[Any]]: + parentnodeids = {n.nodeid for n in node.iter_parents()} + for fixturedef in fixturedefs: + if fixturedef.baseid in parentnodeids: + yield fixturedef + + +def show_fixtures_per_test(config: Config) -> int | ExitCode: + from _pytest.main import wrap_session + + return wrap_session(config, _show_fixtures_per_test) + + +_PYTEST_DIR = Path(_pytest.__file__).parent + + +def _pretty_fixture_path(invocation_dir: Path, func) -> str: + loc = Path(getlocation(func, invocation_dir)) + prefix = Path("...", "_pytest") + try: + return str(prefix / loc.relative_to(_PYTEST_DIR)) + except ValueError: + return bestrelpath(invocation_dir, loc) + + +def _show_fixtures_per_test(config: Config, session: Session) -> None: + import _pytest.config + + session.perform_collect() + invocation_dir = config.invocation_params.dir + tw = _pytest.config.create_terminal_writer(config) + verbose = config.get_verbosity() + + def get_best_relpath(func) -> str: + loc = getlocation(func, invocation_dir) + return bestrelpath(invocation_dir, Path(loc)) + + def write_fixture(fixture_def: FixtureDef[object]) -> None: + argname = fixture_def.argname + if verbose <= 0 and argname.startswith("_"): + return + prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func) + tw.write(f"{argname}", green=True) + tw.write(f" -- {prettypath}", yellow=True) + tw.write("\n") + fixture_doc = inspect.getdoc(fixture_def.func) + if fixture_doc: + write_docstring( + tw, + fixture_doc.split("\n\n", maxsplit=1)[0] + if verbose <= 0 + else fixture_doc, + ) + else: + tw.line(" no docstring available", red=True) + + def write_item(item: nodes.Item) -> None: + # Not all items have _fixtureinfo attribute. + info: FuncFixtureInfo | None = getattr(item, "_fixtureinfo", None) + if info is None or not info.name2fixturedefs: + # This test item does not use any fixtures. + return + tw.line() + tw.sep("-", f"fixtures used by {item.name}") + # TODO: Fix this type ignore. + tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined] + # dict key not used in loop but needed for sorting. + for _, fixturedefs in sorted(info.name2fixturedefs.items()): + assert fixturedefs is not None + if not fixturedefs: + continue + # Last item is expected to be the one used by the test item. + write_fixture(fixturedefs[-1]) + + for session_item in session.items: + write_item(session_item) + + +def showfixtures(config: Config) -> int | ExitCode: + from _pytest.main import wrap_session + + return wrap_session(config, _showfixtures_main) + + +def _showfixtures_main(config: Config, session: Session) -> None: + import _pytest.config + + session.perform_collect() + invocation_dir = config.invocation_params.dir + tw = _pytest.config.create_terminal_writer(config) + verbose = config.get_verbosity() + + fm = session._fixturemanager + + available = [] + seen: set[tuple[str, str]] = set() + + for argname, fixturedefs in fm._arg2fixturedefs.items(): + assert fixturedefs is not None + if not fixturedefs: + continue + for fixturedef in fixturedefs: + loc = getlocation(fixturedef.func, invocation_dir) + if (fixturedef.argname, loc) in seen: + continue + seen.add((fixturedef.argname, loc)) + available.append( + ( + len(fixturedef.baseid), + fixturedef.func.__module__, + _pretty_fixture_path(invocation_dir, fixturedef.func), + fixturedef.argname, + fixturedef, + ) + ) + + available.sort() + currentmodule = None + for baseid, module, prettypath, argname, fixturedef in available: + if currentmodule != module: + if not module.startswith("_pytest."): + tw.line() + tw.sep("-", f"fixtures defined from {module}") + currentmodule = module + if verbose <= 0 and argname.startswith("_"): + continue + tw.write(f"{argname}", green=True) + if fixturedef.scope != "function": + tw.write(f" [{fixturedef.scope} scope]", cyan=True) + tw.write(f" -- {prettypath}", yellow=True) + tw.write("\n") + doc = inspect.getdoc(fixturedef.func) + if doc: + write_docstring( + tw, doc.split("\n\n", maxsplit=1)[0] if verbose <= 0 else doc + ) + else: + tw.line(" no docstring available", red=True) + tw.line() + + +def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None: + for line in doc.split("\n"): + tw.line(indent + line) diff --git a/.venv/Lib/site-packages/_pytest/freeze_support.py b/.venv/Lib/site-packages/_pytest/freeze_support.py new file mode 100644 index 0000000..2ba6f9b --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/freeze_support.py @@ -0,0 +1,45 @@ +"""Provides a function to report all internal modules for using freezing +tools.""" + +from __future__ import annotations + +import types +from typing import Iterator + + +def freeze_includes() -> list[str]: + """Return a list of module names used by pytest that should be + included by cx_freeze.""" + import _pytest + + result = list(_iter_all_modules(_pytest)) + return result + + +def _iter_all_modules( + package: str | types.ModuleType, + prefix: str = "", +) -> Iterator[str]: + """Iterate over the names of all modules that can be found in the given + package, recursively. + + >>> import _pytest + >>> list(_iter_all_modules(_pytest)) + ['_pytest._argcomplete', '_pytest._code.code', ...] + """ + import os + import pkgutil + + if isinstance(package, str): + path = package + else: + # Type ignored because typeshed doesn't define ModuleType.__path__ + # (only defined on packages). + package_path = package.__path__ + path, prefix = package_path[0], package.__name__ + "." + for _, name, is_package in pkgutil.iter_modules([path]): + if is_package: + for m in _iter_all_modules(os.path.join(path, name), prefix=name + "."): + yield prefix + m + else: + yield prefix + name diff --git a/.venv/Lib/site-packages/_pytest/helpconfig.py b/.venv/Lib/site-packages/_pytest/helpconfig.py new file mode 100644 index 0000000..1886d5c --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/helpconfig.py @@ -0,0 +1,276 @@ +# mypy: allow-untyped-defs +"""Version info, help messages, tracing configuration.""" + +from __future__ import annotations + +from argparse import Action +import os +import sys +from typing import Generator + +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import PrintHelp +from _pytest.config.argparsing import Parser +from _pytest.terminal import TerminalReporter +import pytest + + +class HelpAction(Action): + """An argparse Action that will raise an exception in order to skip the + rest of the argument parsing when --help is passed. + + This prevents argparse from quitting due to missing required arguments + when any are defined, for example by ``pytest_addoption``. + This is similar to the way that the builtin argparse --help option is + implemented by raising SystemExit. + """ + + def __init__(self, option_strings, dest=None, default=False, help=None): + super().__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + nargs=0, + help=help, + ) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + # We should only skip the rest of the parsing after preparse is done. + if getattr(parser._parser, "after_preparse", False): + raise PrintHelp + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("debugconfig") + group.addoption( + "--version", + "-V", + action="count", + default=0, + dest="version", + help="Display pytest version and information about plugins. " + "When given twice, also display information about plugins.", + ) + group._addoption( + "-h", + "--help", + action=HelpAction, + dest="help", + help="Show help message and configuration info", + ) + group._addoption( + "-p", + action="append", + dest="plugins", + default=[], + metavar="name", + help="Early-load given plugin module name or entry point (multi-allowed). " + "To avoid loading of plugins, use the `no:` prefix, e.g. " + "`no:doctest`.", + ) + group.addoption( + "--traceconfig", + "--trace-config", + action="store_true", + default=False, + help="Trace considerations of conftest.py files", + ) + group.addoption( + "--debug", + action="store", + nargs="?", + const="pytestdebug.log", + dest="debug", + metavar="DEBUG_FILE_NAME", + help="Store internal tracing debug information in this log file. " + "This file is opened with 'w' and truncated as a result, care advised. " + "Default: pytestdebug.log.", + ) + group._addoption( + "-o", + "--override-ini", + dest="override_ini", + action="append", + help='Override ini option with "option=value" style, ' + "e.g. `-o xfail_strict=True -o cache_dir=cache`.", + ) + + +@pytest.hookimpl(wrapper=True) +def pytest_cmdline_parse() -> Generator[None, Config, Config]: + config = yield + + if config.option.debug: + # --debug | --debug was provided. + path = config.option.debug + debugfile = open(path, "w", encoding="utf-8") + debugfile.write( + "versions pytest-{}, " + "python-{}\ninvocation_dir={}\ncwd={}\nargs={}\n\n".format( + pytest.__version__, + ".".join(map(str, sys.version_info)), + config.invocation_params.dir, + os.getcwd(), + config.invocation_params.args, + ) + ) + config.trace.root.setwriter(debugfile.write) + undo_tracing = config.pluginmanager.enable_tracing() + sys.stderr.write(f"writing pytest debug information to {path}\n") + + def unset_tracing() -> None: + debugfile.close() + sys.stderr.write(f"wrote pytest debug information to {debugfile.name}\n") + config.trace.root.setwriter(None) + undo_tracing() + + config.add_cleanup(unset_tracing) + + return config + + +def showversion(config: Config) -> None: + if config.option.version > 1: + sys.stdout.write( + f"This is pytest version {pytest.__version__}, imported from {pytest.__file__}\n" + ) + plugininfo = getpluginversioninfo(config) + if plugininfo: + for line in plugininfo: + sys.stdout.write(line + "\n") + else: + sys.stdout.write(f"pytest {pytest.__version__}\n") + + +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: + if config.option.version > 0: + showversion(config) + return 0 + elif config.option.help: + config._do_configure() + showhelp(config) + config._ensure_unconfigure() + return 0 + return None + + +def showhelp(config: Config) -> None: + import textwrap + + reporter: TerminalReporter | None = config.pluginmanager.get_plugin( + "terminalreporter" + ) + assert reporter is not None + tw = reporter._tw + tw.write(config._parser.optparser.format_help()) + tw.line() + tw.line( + "[pytest] ini-options in the first " + "pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" + ) + tw.line() + + columns = tw.fullwidth # costly call + indent_len = 24 # based on argparse's max_help_position=24 + indent = " " * indent_len + for name in config._parser._ininames: + help, type, default = config._parser._inidict[name] + if type is None: + type = "string" + if help is None: + raise TypeError(f"help argument cannot be None for {name}") + spec = f"{name} ({type}):" + tw.write(f" {spec}") + spec_len = len(spec) + if spec_len > (indent_len - 3): + # Display help starting at a new line. + tw.line() + helplines = textwrap.wrap( + help, + columns, + initial_indent=indent, + subsequent_indent=indent, + break_on_hyphens=False, + ) + + for line in helplines: + tw.line(line) + else: + # Display help starting after the spec, following lines indented. + tw.write(" " * (indent_len - spec_len - 2)) + wrapped = textwrap.wrap(help, columns - indent_len, break_on_hyphens=False) + + if wrapped: + tw.line(wrapped[0]) + for line in wrapped[1:]: + tw.line(indent + line) + + tw.line() + tw.line("Environment variables:") + vars = [ + ( + "CI", + "When set (regardless of value), pytest knows it is running in a " + "CI process and does not truncate summary info", + ), + ("BUILD_NUMBER", "Equivalent to CI"), + ("PYTEST_ADDOPTS", "Extra command line options"), + ("PYTEST_PLUGINS", "Comma-separated plugins to load during startup"), + ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "Set to disable plugin auto-loading"), + ("PYTEST_DEBUG", "Set to enable debug tracing of pytest's internals"), + ] + for name, help in vars: + tw.line(f" {name:<24} {help}") + tw.line() + tw.line() + + tw.line("to see available markers type: pytest --markers") + tw.line("to see available fixtures type: pytest --fixtures") + tw.line( + "(shown according to specified file_or_dir or current dir " + "if not specified; fixtures with leading '_' are only shown " + "with the '-v' option" + ) + + for warningreport in reporter.stats.get("warnings", []): + tw.line("warning : " + warningreport.message, red=True) + + +conftest_options = [("pytest_plugins", "list of plugin names to load")] + + +def getpluginversioninfo(config: Config) -> list[str]: + lines = [] + plugininfo = config.pluginmanager.list_plugin_distinfo() + if plugininfo: + lines.append("registered third-party plugins:") + for plugin, dist in plugininfo: + loc = getattr(plugin, "__file__", repr(plugin)) + content = f"{dist.project_name}-{dist.version} at {loc}" + lines.append(" " + content) + return lines + + +def pytest_report_header(config: Config) -> list[str]: + lines = [] + if config.option.debug or config.option.traceconfig: + lines.append(f"using: pytest-{pytest.__version__}") + + verinfo = getpluginversioninfo(config) + if verinfo: + lines.extend(verinfo) + + if config.option.traceconfig: + lines.append("active plugins:") + items = config.pluginmanager.list_name_plugin() + for name, plugin in items: + if hasattr(plugin, "__file__"): + r = plugin.__file__ + else: + r = repr(plugin) + lines.append(f" {name:<20}: {r}") + return lines diff --git a/.venv/Lib/site-packages/_pytest/hookspec.py b/.venv/Lib/site-packages/_pytest/hookspec.py new file mode 100644 index 0000000..ed84360 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/hookspec.py @@ -0,0 +1,1333 @@ +# mypy: allow-untyped-defs +# ruff: noqa: T100 +"""Hook specifications for pytest plugins which are invoked by pytest itself +and by builtin plugins.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any +from typing import Mapping +from typing import Sequence +from typing import TYPE_CHECKING + +from pluggy import HookspecMarker + +from .deprecated import HOOK_LEGACY_PATH_ARG + + +if TYPE_CHECKING: + import pdb + from typing import Literal + import warnings + + from _pytest._code.code import ExceptionInfo + from _pytest._code.code import ExceptionRepr + from _pytest.compat import LEGACY_PATH + from _pytest.config import _PluggyPlugin + from _pytest.config import Config + from _pytest.config import ExitCode + from _pytest.config import PytestPluginManager + from _pytest.config.argparsing import Parser + from _pytest.fixtures import FixtureDef + from _pytest.fixtures import SubRequest + from _pytest.main import Session + from _pytest.nodes import Collector + from _pytest.nodes import Item + from _pytest.outcomes import Exit + from _pytest.python import Class + from _pytest.python import Function + from _pytest.python import Metafunc + from _pytest.python import Module + from _pytest.reports import CollectReport + from _pytest.reports import TestReport + from _pytest.runner import CallInfo + from _pytest.terminal import TerminalReporter + from _pytest.terminal import TestShortLogReport + + +hookspec = HookspecMarker("pytest") + +# ------------------------------------------------------------------------- +# Initialization hooks called for every plugin +# ------------------------------------------------------------------------- + + +@hookspec(historic=True) +def pytest_addhooks(pluginmanager: PytestPluginManager) -> None: + """Called at plugin registration time to allow adding new hooks via a call to + :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. + + :param pluginmanager: The pytest plugin manager. + + .. note:: + This hook is incompatible with hook wrappers. + + Use in conftest plugins + ======================= + + If a conftest plugin implements this hook, it will be called immediately + when the conftest is registered. + """ + + +@hookspec(historic=True) +def pytest_plugin_registered( + plugin: _PluggyPlugin, + plugin_name: str, + manager: PytestPluginManager, +) -> None: + """A new pytest plugin got registered. + + :param plugin: The plugin module or instance. + :param plugin_name: The name by which the plugin is registered. + :param manager: The pytest plugin manager. + + .. note:: + This hook is incompatible with hook wrappers. + + Use in conftest plugins + ======================= + + If a conftest plugin implements this hook, it will be called immediately + when the conftest is registered, once for each plugin registered thus far + (including itself!), and for all plugins thereafter when they are + registered. + """ + + +@hookspec(historic=True) +def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: + """Register argparse-style options and ini-style config values, + called once at the beginning of a test run. + + :param parser: + To add command line options, call + :py:func:`parser.addoption(...) `. + To add ini-file values call :py:func:`parser.addini(...) + `. + + :param pluginmanager: + The pytest plugin manager, which can be used to install :py:func:`~pytest.hookspec`'s + or :py:func:`~pytest.hookimpl`'s and allow one plugin to call another plugin's hooks + to change how command line options are added. + + Options can later be accessed through the + :py:class:`config ` object, respectively: + + - :py:func:`config.getoption(name) ` to + retrieve the value of a command line option. + + - :py:func:`config.getini(name) ` to retrieve + a value read from an ini-style file. + + The config object is passed around on many internal objects via the ``.config`` + attribute or can be retrieved as the ``pytestconfig`` fixture. + + .. note:: + This hook is incompatible with hook wrappers. + + Use in conftest plugins + ======================= + + If a conftest plugin implements this hook, it will be called immediately + when the conftest is registered. + + This hook is only called for :ref:`initial conftests `. + """ + + +@hookspec(historic=True) +def pytest_configure(config: Config) -> None: + """Allow plugins and conftest files to perform initial configuration. + + .. note:: + This hook is incompatible with hook wrappers. + + :param config: The pytest config object. + + Use in conftest plugins + ======================= + + This hook is called for every :ref:`initial conftest ` file + after command line options have been parsed. After that, the hook is called + for other conftest files as they are registered. + """ + + +# ------------------------------------------------------------------------- +# Bootstrapping hooks called for plugins registered early enough: +# internal and 3rd party plugins. +# ------------------------------------------------------------------------- + + +@hookspec(firstresult=True) +def pytest_cmdline_parse( + pluginmanager: PytestPluginManager, args: list[str] +) -> Config | None: + """Return an initialized :class:`~pytest.Config`, parsing the specified args. + + Stops at first non-None result, see :ref:`firstresult`. + + .. note:: + This hook is only called for plugin classes passed to the + ``plugins`` arg when using `pytest.main`_ to perform an in-process + test run. + + :param pluginmanager: The pytest plugin manager. + :param args: List of arguments passed on the command line. + :returns: A pytest config object. + + Use in conftest plugins + ======================= + + This hook is not called for conftest files. + """ + + +def pytest_load_initial_conftests( + early_config: Config, parser: Parser, args: list[str] +) -> None: + """Called to implement the loading of :ref:`initial conftest files + ` ahead of command line option parsing. + + :param early_config: The pytest config object. + :param args: Arguments passed on the command line. + :param parser: To add command line options. + + Use in conftest plugins + ======================= + + This hook is not called for conftest files. + """ + + +@hookspec(firstresult=True) +def pytest_cmdline_main(config: Config) -> ExitCode | int | None: + """Called for performing the main command line action. + + The default implementation will invoke the configure hooks and + :hook:`pytest_runtestloop`. + + Stops at first non-None result, see :ref:`firstresult`. + + :param config: The pytest config object. + :returns: The exit code. + + Use in conftest plugins + ======================= + + This hook is only called for :ref:`initial conftests `. + """ + + +# ------------------------------------------------------------------------- +# collection hooks +# ------------------------------------------------------------------------- + + +@hookspec(firstresult=True) +def pytest_collection(session: Session) -> object | None: + """Perform the collection phase for the given session. + + Stops at first non-None result, see :ref:`firstresult`. + The return value is not used, but only stops further processing. + + The default collection phase is this (see individual hooks for full details): + + 1. Starting from ``session`` as the initial collector: + + 1. ``pytest_collectstart(collector)`` + 2. ``report = pytest_make_collect_report(collector)`` + 3. ``pytest_exception_interact(collector, call, report)`` if an interactive exception occurred + 4. For each collected node: + + 1. If an item, ``pytest_itemcollected(item)`` + 2. If a collector, recurse into it. + + 5. ``pytest_collectreport(report)`` + + 2. ``pytest_collection_modifyitems(session, config, items)`` + + 1. ``pytest_deselected(items)`` for any deselected items (may be called multiple times) + + 3. ``pytest_collection_finish(session)`` + 4. Set ``session.items`` to the list of collected items + 5. Set ``session.testscollected`` to the number of collected items + + You can implement this hook to only perform some action before collection, + for example the terminal plugin uses it to start displaying the collection + counter (and returns `None`). + + :param session: The pytest session object. + + Use in conftest plugins + ======================= + + This hook is only called for :ref:`initial conftests `. + """ + + +def pytest_collection_modifyitems( + session: Session, config: Config, items: list[Item] +) -> None: + """Called after collection has been performed. May filter or re-order + the items in-place. + + When items are deselected (filtered out from ``items``), + the hook :hook:`pytest_deselected` must be called explicitly + with the deselected items to properly notify other plugins, + e.g. with ``config.hook.pytest_deselected(items=deselected_items)``. + + :param session: The pytest session object. + :param config: The pytest config object. + :param items: List of item objects. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ + + +def pytest_collection_finish(session: Session) -> None: + """Called after collection has been performed and modified. + + :param session: The pytest session object. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ + + +@hookspec( + firstresult=True, + warn_on_impl_args={ + "path": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="path", pathlib_path_arg="collection_path" + ), + }, +) +def pytest_ignore_collect( + collection_path: Path, path: LEGACY_PATH, config: Config +) -> bool | None: + """Return ``True`` to ignore this path for collection. + + Return ``None`` to let other plugins ignore the path for collection. + + Returning ``False`` will forcefully *not* ignore this path for collection, + without giving a chance for other plugins to ignore this path. + + This hook is consulted for all files and directories prior to calling + more specific hooks. + + Stops at first non-None result, see :ref:`firstresult`. + + :param collection_path: The path to analyze. + :type collection_path: pathlib.Path + :param path: The path to analyze (deprecated). + :param config: The pytest config object. + + .. versionchanged:: 7.0.0 + The ``collection_path`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``path`` parameter. The ``path`` parameter + has been deprecated. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collection path, only + conftest files in parent directories of the collection path are consulted + (if the path is a directory, its own conftest file is *not* consulted - a + directory cannot ignore itself!). + """ + + +@hookspec(firstresult=True) +def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None: + """Create a :class:`~pytest.Collector` for the given directory, or None if + not relevant. + + .. versionadded:: 8.0 + + For best results, the returned collector should be a subclass of + :class:`~pytest.Directory`, but this is not required. + + The new node needs to have the specified ``parent`` as a parent. + + Stops at first non-None result, see :ref:`firstresult`. + + :param path: The path to analyze. + :type path: pathlib.Path + + See :ref:`custom directory collectors` for a simple example of use of this + hook. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collection path, only + conftest files in parent directories of the collection path are consulted + (if the path is a directory, its own conftest file is *not* consulted - a + directory cannot collect itself!). + """ + + +@hookspec( + warn_on_impl_args={ + "path": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="path", pathlib_path_arg="file_path" + ), + }, +) +def pytest_collect_file( + file_path: Path, path: LEGACY_PATH, parent: Collector +) -> Collector | None: + """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. + + For best results, the returned collector should be a subclass of + :class:`~pytest.File`, but this is not required. + + The new node needs to have the specified ``parent`` as a parent. + + :param file_path: The path to analyze. + :type file_path: pathlib.Path + :param path: The path to collect (deprecated). + + .. versionchanged:: 7.0.0 + The ``file_path`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``path`` parameter. The ``path`` parameter + has been deprecated. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given file path, only + conftest files in parent directories of the file path are consulted. + """ + + +# logging hooks for collection + + +def pytest_collectstart(collector: Collector) -> None: + """Collector starts collecting. + + :param collector: + The collector. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collector, only + conftest files in the collector's directory and its parent directories are + consulted. + """ + + +def pytest_itemcollected(item: Item) -> None: + """We just collected a test item. + + :param item: + The item. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +def pytest_collectreport(report: CollectReport) -> None: + """Collector finished collecting. + + :param report: + The collect report. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collector, only + conftest files in the collector's directory and its parent directories are + consulted. + """ + + +def pytest_deselected(items: Sequence[Item]) -> None: + """Called for deselected test items, e.g. by keyword. + + Note that this hook has two integration aspects for plugins: + + - it can be *implemented* to be notified of deselected items + - it must be *called* from :hook:`pytest_collection_modifyitems` + implementations when items are deselected (to properly notify other plugins). + + May be called multiple times. + + :param items: + The items. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. + """ + + +@hookspec(firstresult=True) +def pytest_make_collect_report(collector: Collector) -> CollectReport | None: + """Perform :func:`collector.collect() ` and return + a :class:`~pytest.CollectReport`. + + Stops at first non-None result, see :ref:`firstresult`. + + :param collector: + The collector. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collector, only + conftest files in the collector's directory and its parent directories are + consulted. + """ + + +# ------------------------------------------------------------------------- +# Python test function related hooks +# ------------------------------------------------------------------------- + + +@hookspec( + firstresult=True, + warn_on_impl_args={ + "path": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="path", pathlib_path_arg="module_path" + ), + }, +) +def pytest_pycollect_makemodule( + module_path: Path, path: LEGACY_PATH, parent +) -> Module | None: + """Return a :class:`pytest.Module` collector or None for the given path. + + This hook will be called for each matching test module path. + The :hook:`pytest_collect_file` hook needs to be used if you want to + create test modules for files that do not match as a test module. + + Stops at first non-None result, see :ref:`firstresult`. + + :param module_path: The path of the module to collect. + :type module_path: pathlib.Path + :param path: The path of the module to collect (deprecated). + + .. versionchanged:: 7.0.0 + The ``module_path`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``path`` parameter. + + The ``path`` parameter has been deprecated in favor of ``fspath``. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given parent collector, + only conftest files in the collector's directory and its parent directories + are consulted. + """ + + +@hookspec(firstresult=True) +def pytest_pycollect_makeitem( + collector: Module | Class, name: str, obj: object +) -> None | Item | Collector | list[Item | Collector]: + """Return a custom item/collector for a Python object in a module, or None. + + Stops at first non-None result, see :ref:`firstresult`. + + :param collector: + The module/class collector. + :param name: + The name of the object in the module/class. + :param obj: + The object. + :returns: + The created items/collectors. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given collector, only + conftest files in the collector's directory and its parent directories + are consulted. + """ + + +@hookspec(firstresult=True) +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: + """Call underlying test function. + + Stops at first non-None result, see :ref:`firstresult`. + + :param pyfuncitem: + The function item. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only + conftest files in the item's directory and its parent directories + are consulted. + """ + + +def pytest_generate_tests(metafunc: Metafunc) -> None: + """Generate (multiple) parametrized calls to a test function. + + :param metafunc: + The :class:`~pytest.Metafunc` helper for the test function. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given function definition, + only conftest files in the functions's directory and its parent directories + are consulted. + """ + + +@hookspec(firstresult=True) +def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str | None: + """Return a user-friendly string representation of the given ``val`` + that will be used by @pytest.mark.parametrize calls, or None if the hook + doesn't know about ``val``. + + The parameter name is available as ``argname``, if required. + + Stops at first non-None result, see :ref:`firstresult`. + + :param config: The pytest config object. + :param val: The parametrized value. + :param argname: The automatic parameter name produced by pytest. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. + """ + + +# ------------------------------------------------------------------------- +# runtest related hooks +# ------------------------------------------------------------------------- + + +@hookspec(firstresult=True) +def pytest_runtestloop(session: Session) -> object | None: + """Perform the main runtest loop (after collection finished). + + The default hook implementation performs the runtest protocol for all items + collected in the session (``session.items``), unless the collection failed + or the ``collectonly`` pytest option is set. + + If at any point :py:func:`pytest.exit` is called, the loop is + terminated immediately. + + If at any point ``session.shouldfail`` or ``session.shouldstop`` are set, the + loop is terminated after the runtest protocol for the current item is finished. + + :param session: The pytest session object. + + Stops at first non-None result, see :ref:`firstresult`. + The return value is not used, but only stops further processing. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. + """ + + +@hookspec(firstresult=True) +def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None: + """Perform the runtest protocol for a single test item. + + The default runtest protocol is this (see individual hooks for full details): + + - ``pytest_runtest_logstart(nodeid, location)`` + + - Setup phase: + - ``call = pytest_runtest_setup(item)`` (wrapped in ``CallInfo(when="setup")``) + - ``report = pytest_runtest_makereport(item, call)`` + - ``pytest_runtest_logreport(report)`` + - ``pytest_exception_interact(call, report)`` if an interactive exception occurred + + - Call phase, if the setup passed and the ``setuponly`` pytest option is not set: + - ``call = pytest_runtest_call(item)`` (wrapped in ``CallInfo(when="call")``) + - ``report = pytest_runtest_makereport(item, call)`` + - ``pytest_runtest_logreport(report)`` + - ``pytest_exception_interact(call, report)`` if an interactive exception occurred + + - Teardown phase: + - ``call = pytest_runtest_teardown(item, nextitem)`` (wrapped in ``CallInfo(when="teardown")``) + - ``report = pytest_runtest_makereport(item, call)`` + - ``pytest_runtest_logreport(report)`` + - ``pytest_exception_interact(call, report)`` if an interactive exception occurred + + - ``pytest_runtest_logfinish(nodeid, location)`` + + :param item: Test item for which the runtest protocol is performed. + :param nextitem: The scheduled-to-be-next test item (or None if this is the end my friend). + + Stops at first non-None result, see :ref:`firstresult`. + The return value is not used, but only stops further processing. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. + """ + + +def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) -> None: + """Called at the start of running the runtest protocol for a single item. + + See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. + + :param nodeid: Full node ID of the item. + :param location: A tuple of ``(filename, lineno, testname)`` + where ``filename`` is a file path relative to ``config.rootpath`` + and ``lineno`` is 0-based. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +def pytest_runtest_logfinish( + nodeid: str, location: tuple[str, int | None, str] +) -> None: + """Called at the end of running the runtest protocol for a single item. + + See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. + + :param nodeid: Full node ID of the item. + :param location: A tuple of ``(filename, lineno, testname)`` + where ``filename`` is a file path relative to ``config.rootpath`` + and ``lineno`` is 0-based. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +def pytest_runtest_setup(item: Item) -> None: + """Called to perform the setup phase for a test item. + + The default implementation runs ``setup()`` on ``item`` and all of its + parents (which haven't been setup yet). This includes obtaining the + values of fixtures required by the item (which haven't been obtained + yet). + + :param item: + The item. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +def pytest_runtest_call(item: Item) -> None: + """Called to run the test for test item (the call phase). + + The default implementation calls ``item.runtest()``. + + :param item: + The item. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: + """Called to perform the teardown phase for a test item. + + The default implementation runs the finalizers and calls ``teardown()`` + on ``item`` and all of its parents (which need to be torn down). This + includes running the teardown phase of fixtures required by the item (if + they go out of scope). + + :param item: + The item. + :param nextitem: + The scheduled-to-be-next test item (None if no further test item is + scheduled). This argument is used to perform exact teardowns, i.e. + calling just enough finalizers so that nextitem only needs to call + setup functions. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +@hookspec(firstresult=True) +def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None: + """Called to create a :class:`~pytest.TestReport` for each of + the setup, call and teardown runtest phases of a test item. + + See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. + + :param item: The item. + :param call: The :class:`~pytest.CallInfo` for the phase. + + Stops at first non-None result, see :ref:`firstresult`. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +def pytest_runtest_logreport(report: TestReport) -> None: + """Process the :class:`~pytest.TestReport` produced for each + of the setup, call and teardown runtest phases of an item. + + See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +@hookspec(firstresult=True) +def pytest_report_to_serializable( + config: Config, + report: CollectReport | TestReport, +) -> dict[str, Any] | None: + """Serialize the given report object into a data structure suitable for + sending over the wire, e.g. converted to JSON. + + :param config: The pytest config object. + :param report: The report. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. The exact details may depend + on the plugin which calls the hook. + """ + + +@hookspec(firstresult=True) +def pytest_report_from_serializable( + config: Config, + data: dict[str, Any], +) -> CollectReport | TestReport | None: + """Restore a report object previously serialized with + :hook:`pytest_report_to_serializable`. + + :param config: The pytest config object. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. The exact details may depend + on the plugin which calls the hook. + """ + + +# ------------------------------------------------------------------------- +# Fixture related hooks +# ------------------------------------------------------------------------- + + +@hookspec(firstresult=True) +def pytest_fixture_setup( + fixturedef: FixtureDef[Any], request: SubRequest +) -> object | None: + """Perform fixture setup execution. + + :param fixturedef: + The fixture definition object. + :param request: + The fixture request object. + :returns: + The return value of the call to the fixture function. + + Stops at first non-None result, see :ref:`firstresult`. + + .. note:: + If the fixture function returns None, other implementations of + this hook function will continue to be called, according to the + behavior of the :ref:`firstresult` option. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given fixture, only + conftest files in the fixture scope's directory and its parent directories + are consulted. + """ + + +def pytest_fixture_post_finalizer( + fixturedef: FixtureDef[Any], request: SubRequest +) -> None: + """Called after fixture teardown, but before the cache is cleared, so + the fixture result ``fixturedef.cached_result`` is still available (not + ``None``). + + :param fixturedef: + The fixture definition object. + :param request: + The fixture request object. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given fixture, only + conftest files in the fixture scope's directory and its parent directories + are consulted. + """ + + +# ------------------------------------------------------------------------- +# test session related hooks +# ------------------------------------------------------------------------- + + +def pytest_sessionstart(session: Session) -> None: + """Called after the ``Session`` object has been created and before performing collection + and entering the run test loop. + + :param session: The pytest session object. + + Use in conftest plugins + ======================= + + This hook is only called for :ref:`initial conftests `. + """ + + +def pytest_sessionfinish( + session: Session, + exitstatus: int | ExitCode, +) -> None: + """Called after whole test run finished, right before returning the exit status to the system. + + :param session: The pytest session object. + :param exitstatus: The status which pytest will return to the system. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. + """ + + +def pytest_unconfigure(config: Config) -> None: + """Called before test process is exited. + + :param config: The pytest config object. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. + """ + + +# ------------------------------------------------------------------------- +# hooks for customizing the assert methods +# ------------------------------------------------------------------------- + + +def pytest_assertrepr_compare( + config: Config, op: str, left: object, right: object +) -> list[str] | None: + """Return explanation for comparisons in failing assert expressions. + + Return None for no custom explanation, otherwise return a list + of strings. The strings will be joined by newlines but any newlines + *in* a string will be escaped. Note that all but the first line will + be indented slightly, the intention is for the first line to be a summary. + + :param config: The pytest config object. + :param op: The operator, e.g. `"=="`, `"!="`, `"not in"`. + :param left: The left operand. + :param right: The right operand. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None: + """Called whenever an assertion passes. + + .. versionadded:: 5.0 + + Use this hook to do some processing after a passing assertion. + The original assertion information is available in the `orig` string + and the pytest introspected assertion information is available in the + `expl` string. + + This hook must be explicitly enabled by the ``enable_assertion_pass_hook`` + ini-file option: + + .. code-block:: ini + + [pytest] + enable_assertion_pass_hook=true + + You need to **clean the .pyc** files in your project directory and interpreter libraries + when enabling this option, as assertions will require to be re-written. + + :param item: pytest item object of current test. + :param lineno: Line number of the assert statement. + :param orig: String with the original assertion. + :param expl: String with the assert explanation. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in the item's directory and its parent directories are consulted. + """ + + +# ------------------------------------------------------------------------- +# Hooks for influencing reporting (invoked from _pytest_terminal). +# ------------------------------------------------------------------------- + + +@hookspec( + warn_on_impl_args={ + "startdir": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="startdir", pathlib_path_arg="start_path" + ), + }, +) +def pytest_report_header( # type:ignore[empty-body] + config: Config, start_path: Path, startdir: LEGACY_PATH +) -> str | list[str]: + """Return a string or list of strings to be displayed as header info for terminal reporting. + + :param config: The pytest config object. + :param start_path: The starting dir. + :type start_path: pathlib.Path + :param startdir: The starting dir (deprecated). + + .. note:: + + Lines returned by a plugin are displayed before those of plugins which + ran before it. + If you want to have your line(s) displayed first, use + :ref:`trylast=True `. + + .. versionchanged:: 7.0.0 + The ``start_path`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``startdir`` parameter. The ``startdir`` parameter + has been deprecated. + + Use in conftest plugins + ======================= + + This hook is only called for :ref:`initial conftests `. + """ + + +@hookspec( + warn_on_impl_args={ + "startdir": HOOK_LEGACY_PATH_ARG.format( + pylib_path_arg="startdir", pathlib_path_arg="start_path" + ), + }, +) +def pytest_report_collectionfinish( # type:ignore[empty-body] + config: Config, + start_path: Path, + startdir: LEGACY_PATH, + items: Sequence[Item], +) -> str | list[str]: + """Return a string or list of strings to be displayed after collection + has finished successfully. + + These strings will be displayed after the standard "collected X items" message. + + .. versionadded:: 3.2 + + :param config: The pytest config object. + :param start_path: The starting dir. + :type start_path: pathlib.Path + :param startdir: The starting dir (deprecated). + :param items: List of pytest items that are going to be executed; this list should not be modified. + + .. note:: + + Lines returned by a plugin are displayed before those of plugins which + ran before it. + If you want to have your line(s) displayed first, use + :ref:`trylast=True `. + + .. versionchanged:: 7.0.0 + The ``start_path`` parameter was added as a :class:`pathlib.Path` + equivalent of the ``startdir`` parameter. The ``startdir`` parameter + has been deprecated. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ + + +@hookspec(firstresult=True) +def pytest_report_teststatus( # type:ignore[empty-body] + report: CollectReport | TestReport, config: Config +) -> TestShortLogReport | tuple[str, str, str | tuple[str, Mapping[str, bool]]]: + """Return result-category, shortletter and verbose word for status + reporting. + + The result-category is a category in which to count the result, for + example "passed", "skipped", "error" or the empty string. + + The shortletter is shown as testing progresses, for example ".", "s", + "E" or the empty string. + + The verbose word is shown as testing progresses in verbose mode, for + example "PASSED", "SKIPPED", "ERROR" or the empty string. + + pytest may style these implicitly according to the report outcome. + To provide explicit styling, return a tuple for the verbose word, + for example ``"rerun", "R", ("RERUN", {"yellow": True})``. + + :param report: The report object whose status is to be returned. + :param config: The pytest config object. + :returns: The test status. + + Stops at first non-None result, see :ref:`firstresult`. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ + + +def pytest_terminal_summary( + terminalreporter: TerminalReporter, + exitstatus: ExitCode, + config: Config, +) -> None: + """Add a section to terminal summary reporting. + + :param terminalreporter: The internal terminal reporter object. + :param exitstatus: The exit status that will be reported back to the OS. + :param config: The pytest config object. + + .. versionadded:: 4.2 + The ``config`` parameter. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ + + +@hookspec(historic=True) +def pytest_warning_recorded( + warning_message: warnings.WarningMessage, + when: Literal["config", "collect", "runtest"], + nodeid: str, + location: tuple[str, int, str] | None, +) -> None: + """Process a warning captured by the internal pytest warnings plugin. + + :param warning_message: + The captured warning. This is the same object produced by :class:`warnings.catch_warnings`, + and contains the same attributes as the parameters of :py:func:`warnings.showwarning`. + + :param when: + Indicates when the warning was captured. Possible values: + + * ``"config"``: during pytest configuration/initialization stage. + * ``"collect"``: during test collection. + * ``"runtest"``: during test execution. + + :param nodeid: + Full id of the item. Empty string for warnings that are not specific to + a particular node. + + :param location: + When available, holds information about the execution context of the captured + warning (filename, linenumber, function). ``function`` evaluates to + when the execution context is at the module level. + + .. versionadded:: 6.0 + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. If the warning is specific to a + particular node, only conftest files in parent directories of the node are + consulted. + """ + + +# ------------------------------------------------------------------------- +# Hooks for influencing skipping +# ------------------------------------------------------------------------- + + +def pytest_markeval_namespace( # type:ignore[empty-body] + config: Config, +) -> dict[str, Any]: + """Called when constructing the globals dictionary used for + evaluating string conditions in xfail/skipif markers. + + This is useful when the condition for a marker requires + objects that are expensive or impossible to obtain during + collection time, which is required by normal boolean + conditions. + + .. versionadded:: 6.2 + + :param config: The pytest config object. + :returns: A dictionary of additional globals to add. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given item, only conftest + files in parent directories of the item are consulted. + """ + + +# ------------------------------------------------------------------------- +# error handling and internal debugging hooks +# ------------------------------------------------------------------------- + + +def pytest_internalerror( + excrepr: ExceptionRepr, + excinfo: ExceptionInfo[BaseException], +) -> bool | None: + """Called for internal errors. + + Return True to suppress the fallback handling of printing an + INTERNALERROR message directly to sys.stderr. + + :param excrepr: The exception repr object. + :param excinfo: The exception info. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ + + +def pytest_keyboard_interrupt( + excinfo: ExceptionInfo[KeyboardInterrupt | Exit], +) -> None: + """Called for keyboard interrupt. + + :param excinfo: The exception info. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ + + +def pytest_exception_interact( + node: Item | Collector, + call: CallInfo[Any], + report: CollectReport | TestReport, +) -> None: + """Called when an exception was raised which can potentially be + interactively handled. + + May be called during collection (see :hook:`pytest_make_collect_report`), + in which case ``report`` is a :class:`~pytest.CollectReport`. + + May be called during runtest of an item (see :hook:`pytest_runtest_protocol`), + in which case ``report`` is a :class:`~pytest.TestReport`. + + This hook is not called if the exception that was raised is an internal + exception like ``skip.Exception``. + + :param node: + The item or collector. + :param call: + The call information. Contains the exception. + :param report: + The collection or test report. + + Use in conftest plugins + ======================= + + Any conftest file can implement this hook. For a given node, only conftest + files in parent directories of the node are consulted. + """ + + +def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None: + """Called upon pdb.set_trace(). + + Can be used by plugins to take special action just before the python + debugger enters interactive mode. + + :param config: The pytest config object. + :param pdb: The Pdb instance. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ + + +def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None: + """Called when leaving pdb (e.g. with continue after pdb.set_trace()). + + Can be used by plugins to take special action just after the python + debugger leaves interactive mode. + + :param config: The pytest config object. + :param pdb: The Pdb instance. + + Use in conftest plugins + ======================= + + Any conftest plugin can implement this hook. + """ diff --git a/.venv/Lib/site-packages/_pytest/junitxml.py b/.venv/Lib/site-packages/_pytest/junitxml.py new file mode 100644 index 0000000..3a2cb59 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/junitxml.py @@ -0,0 +1,697 @@ +# mypy: allow-untyped-defs +"""Report test results in JUnit-XML format, for use with Jenkins and build +integration servers. + +Based on initial code from Ross Lawley. + +Output conforms to +https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd +""" + +from __future__ import annotations + +from datetime import datetime +from datetime import timezone +import functools +import os +import platform +import re +from typing import Callable +from typing import Match +import xml.etree.ElementTree as ET + +from _pytest import nodes +from _pytest import timing +from _pytest._code.code import ExceptionRepr +from _pytest._code.code import ReprFileLocation +from _pytest.config import Config +from _pytest.config import filename_arg +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureRequest +from _pytest.reports import TestReport +from _pytest.stash import StashKey +from _pytest.terminal import TerminalReporter +import pytest + + +xml_key = StashKey["LogXML"]() + + +def bin_xml_escape(arg: object) -> str: + r"""Visually escape invalid XML characters. + + For example, transforms + 'hello\aworld\b' + into + 'hello#x07world#x08' + Note that the #xABs are *not* XML escapes - missing the ampersand «. + The idea is to escape visually for the user rather than for XML itself. + """ + + def repl(matchobj: Match[str]) -> str: + i = ord(matchobj.group()) + if i <= 0xFF: + return f"#x{i:02X}" + else: + return f"#x{i:04X}" + + # The spec range of valid chars is: + # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + # For an unknown(?) reason, we disallow #x7F (DEL) as well. + illegal_xml_re = ( + "[^\u0009\u000a\u000d\u0020-\u007e\u0080-\ud7ff\ue000-\ufffd\u10000-\u10ffff]" + ) + return re.sub(illegal_xml_re, repl, str(arg)) + + +def merge_family(left, right) -> None: + result = {} + for kl, vl in left.items(): + for kr, vr in right.items(): + if not isinstance(vl, list): + raise TypeError(type(vl)) + result[kl] = vl + vr + left.update(result) + + +families = {} +families["_base"] = {"testcase": ["classname", "name"]} +families["_base_legacy"] = {"testcase": ["file", "line", "url"]} + +# xUnit 1.x inherits legacy attributes. +families["xunit1"] = families["_base"].copy() +merge_family(families["xunit1"], families["_base_legacy"]) + +# xUnit 2.x uses strict base attributes. +families["xunit2"] = families["_base"] + + +class _NodeReporter: + def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None: + self.id = nodeid + self.xml = xml + self.add_stats = self.xml.add_stats + self.family = self.xml.family + self.duration = 0.0 + self.properties: list[tuple[str, str]] = [] + self.nodes: list[ET.Element] = [] + self.attrs: dict[str, str] = {} + + def append(self, node: ET.Element) -> None: + self.xml.add_stats(node.tag) + self.nodes.append(node) + + def add_property(self, name: str, value: object) -> None: + self.properties.append((str(name), bin_xml_escape(value))) + + def add_attribute(self, name: str, value: object) -> None: + self.attrs[str(name)] = bin_xml_escape(value) + + def make_properties_node(self) -> ET.Element | None: + """Return a Junit node containing custom properties, if any.""" + if self.properties: + properties = ET.Element("properties") + for name, value in self.properties: + properties.append(ET.Element("property", name=name, value=value)) + return properties + return None + + def record_testreport(self, testreport: TestReport) -> None: + names = mangle_test_address(testreport.nodeid) + existing_attrs = self.attrs + classnames = names[:-1] + if self.xml.prefix: + classnames.insert(0, self.xml.prefix) + attrs: dict[str, str] = { + "classname": ".".join(classnames), + "name": bin_xml_escape(names[-1]), + "file": testreport.location[0], + } + if testreport.location[1] is not None: + attrs["line"] = str(testreport.location[1]) + if hasattr(testreport, "url"): + attrs["url"] = testreport.url + self.attrs = attrs + self.attrs.update(existing_attrs) # Restore any user-defined attributes. + + # Preserve legacy testcase behavior. + if self.family == "xunit1": + return + + # Filter out attributes not permitted by this test family. + # Including custom attributes because they are not valid here. + temp_attrs = {} + for key in self.attrs: + if key in families[self.family]["testcase"]: + temp_attrs[key] = self.attrs[key] + self.attrs = temp_attrs + + def to_xml(self) -> ET.Element: + testcase = ET.Element("testcase", self.attrs, time=f"{self.duration:.3f}") + properties = self.make_properties_node() + if properties is not None: + testcase.append(properties) + testcase.extend(self.nodes) + return testcase + + def _add_simple(self, tag: str, message: str, data: str | None = None) -> None: + node = ET.Element(tag, message=message) + node.text = bin_xml_escape(data) + self.append(node) + + def write_captured_output(self, report: TestReport) -> None: + if not self.xml.log_passing_tests and report.passed: + return + + content_out = report.capstdout + content_log = report.caplog + content_err = report.capstderr + if self.xml.logging == "no": + return + content_all = "" + if self.xml.logging in ["log", "all"]: + content_all = self._prepare_content(content_log, " Captured Log ") + if self.xml.logging in ["system-out", "out-err", "all"]: + content_all += self._prepare_content(content_out, " Captured Out ") + self._write_content(report, content_all, "system-out") + content_all = "" + if self.xml.logging in ["system-err", "out-err", "all"]: + content_all += self._prepare_content(content_err, " Captured Err ") + self._write_content(report, content_all, "system-err") + content_all = "" + if content_all: + self._write_content(report, content_all, "system-out") + + def _prepare_content(self, content: str, header: str) -> str: + return "\n".join([header.center(80, "-"), content, ""]) + + def _write_content(self, report: TestReport, content: str, jheader: str) -> None: + tag = ET.Element(jheader) + tag.text = bin_xml_escape(content) + self.append(tag) + + def append_pass(self, report: TestReport) -> None: + self.add_stats("passed") + + def append_failure(self, report: TestReport) -> None: + # msg = str(report.longrepr.reprtraceback.extraline) + if hasattr(report, "wasxfail"): + self._add_simple("skipped", "xfail-marked test passes unexpectedly") + else: + assert report.longrepr is not None + reprcrash: ReprFileLocation | None = getattr( + report.longrepr, "reprcrash", None + ) + if reprcrash is not None: + message = reprcrash.message + else: + message = str(report.longrepr) + message = bin_xml_escape(message) + self._add_simple("failure", message, str(report.longrepr)) + + def append_collect_error(self, report: TestReport) -> None: + # msg = str(report.longrepr.reprtraceback.extraline) + assert report.longrepr is not None + self._add_simple("error", "collection failure", str(report.longrepr)) + + def append_collect_skipped(self, report: TestReport) -> None: + self._add_simple("skipped", "collection skipped", str(report.longrepr)) + + def append_error(self, report: TestReport) -> None: + assert report.longrepr is not None + reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None) + if reprcrash is not None: + reason = reprcrash.message + else: + reason = str(report.longrepr) + + if report.when == "teardown": + msg = f'failed on teardown with "{reason}"' + else: + msg = f'failed on setup with "{reason}"' + self._add_simple("error", bin_xml_escape(msg), str(report.longrepr)) + + def append_skipped(self, report: TestReport) -> None: + if hasattr(report, "wasxfail"): + xfailreason = report.wasxfail + if xfailreason.startswith("reason: "): + xfailreason = xfailreason[8:] + xfailreason = bin_xml_escape(xfailreason) + skipped = ET.Element("skipped", type="pytest.xfail", message=xfailreason) + self.append(skipped) + else: + assert isinstance(report.longrepr, tuple) + filename, lineno, skipreason = report.longrepr + if skipreason.startswith("Skipped: "): + skipreason = skipreason[9:] + details = f"{filename}:{lineno}: {skipreason}" + + skipped = ET.Element( + "skipped", type="pytest.skip", message=bin_xml_escape(skipreason) + ) + skipped.text = bin_xml_escape(details) + self.append(skipped) + self.write_captured_output(report) + + def finalize(self) -> None: + data = self.to_xml() + self.__dict__.clear() + # Type ignored because mypy doesn't like overriding a method. + # Also the return value doesn't match... + self.to_xml = lambda: data # type: ignore[method-assign] + + +def _warn_incompatibility_with_xunit2( + request: FixtureRequest, fixture_name: str +) -> None: + """Emit a PytestWarning about the given fixture being incompatible with newer xunit revisions.""" + from _pytest.warning_types import PytestWarning + + xml = request.config.stash.get(xml_key, None) + if xml is not None and xml.family not in ("xunit1", "legacy"): + request.node.warn( + PytestWarning( + f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')" + ) + ) + + +@pytest.fixture +def record_property(request: FixtureRequest) -> Callable[[str, object], None]: + """Add extra properties to the calling test. + + User properties become part of the test report and are available to the + configured reporters, like JUnit XML. + + The fixture is callable with ``name, value``. The value is automatically + XML-encoded. + + Example:: + + def test_function(record_property): + record_property("example_key", 1) + """ + _warn_incompatibility_with_xunit2(request, "record_property") + + def append_property(name: str, value: object) -> None: + request.node.user_properties.append((name, value)) + + return append_property + + +@pytest.fixture +def record_xml_attribute(request: FixtureRequest) -> Callable[[str, object], None]: + """Add extra xml attributes to the tag for the calling test. + + The fixture is callable with ``name, value``. The value is + automatically XML-encoded. + """ + from _pytest.warning_types import PytestExperimentalApiWarning + + request.node.warn( + PytestExperimentalApiWarning("record_xml_attribute is an experimental feature") + ) + + _warn_incompatibility_with_xunit2(request, "record_xml_attribute") + + # Declare noop + def add_attr_noop(name: str, value: object) -> None: + pass + + attr_func = add_attr_noop + + xml = request.config.stash.get(xml_key, None) + if xml is not None: + node_reporter = xml.node_reporter(request.node.nodeid) + attr_func = node_reporter.add_attribute + + return attr_func + + +def _check_record_param_type(param: str, v: str) -> None: + """Used by record_testsuite_property to check that the given parameter name is of the proper + type.""" + __tracebackhide__ = True + if not isinstance(v, str): + msg = "{param} parameter needs to be a string, but {g} given" # type: ignore[unreachable] + raise TypeError(msg.format(param=param, g=type(v).__name__)) + + +@pytest.fixture(scope="session") +def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object], None]: + """Record a new ```` tag as child of the root ````. + + This is suitable to writing global information regarding the entire test + suite, and is compatible with ``xunit2`` JUnit family. + + This is a ``session``-scoped fixture which is called with ``(name, value)``. Example: + + .. code-block:: python + + def test_foo(record_testsuite_property): + record_testsuite_property("ARCH", "PPC") + record_testsuite_property("STORAGE_TYPE", "CEPH") + + :param name: + The property name. + :param value: + The property value. Will be converted to a string. + + .. warning:: + + Currently this fixture **does not work** with the + `pytest-xdist `__ plugin. See + :issue:`7767` for details. + """ + __tracebackhide__ = True + + def record_func(name: str, value: object) -> None: + """No-op function in case --junit-xml was not passed in the command-line.""" + __tracebackhide__ = True + _check_record_param_type("name", name) + + xml = request.config.stash.get(xml_key, None) + if xml is not None: + record_func = xml.add_global_property + return record_func + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("terminal reporting") + group.addoption( + "--junitxml", + "--junit-xml", + action="store", + dest="xmlpath", + metavar="path", + type=functools.partial(filename_arg, optname="--junitxml"), + default=None, + help="Create junit-xml style report file at given path", + ) + group.addoption( + "--junitprefix", + "--junit-prefix", + action="store", + metavar="str", + default=None, + help="Prepend prefix to classnames in junit-xml output", + ) + parser.addini( + "junit_suite_name", "Test suite name for JUnit report", default="pytest" + ) + parser.addini( + "junit_logging", + "Write captured log messages to JUnit report: " + "one of no|log|system-out|system-err|out-err|all", + default="no", + ) + parser.addini( + "junit_log_passing_tests", + "Capture log information for passing tests to JUnit report: ", + type="bool", + default=True, + ) + parser.addini( + "junit_duration_report", + "Duration time to report: one of total|call", + default="total", + ) # choices=['total', 'call']) + parser.addini( + "junit_family", + "Emit XML for schema: one of legacy|xunit1|xunit2", + default="xunit2", + ) + + +def pytest_configure(config: Config) -> None: + xmlpath = config.option.xmlpath + # Prevent opening xmllog on worker nodes (xdist). + if xmlpath and not hasattr(config, "workerinput"): + junit_family = config.getini("junit_family") + config.stash[xml_key] = LogXML( + xmlpath, + config.option.junitprefix, + config.getini("junit_suite_name"), + config.getini("junit_logging"), + config.getini("junit_duration_report"), + junit_family, + config.getini("junit_log_passing_tests"), + ) + config.pluginmanager.register(config.stash[xml_key]) + + +def pytest_unconfigure(config: Config) -> None: + xml = config.stash.get(xml_key, None) + if xml: + del config.stash[xml_key] + config.pluginmanager.unregister(xml) + + +def mangle_test_address(address: str) -> list[str]: + path, possible_open_bracket, params = address.partition("[") + names = path.split("::") + # Convert file path to dotted path. + names[0] = names[0].replace(nodes.SEP, ".") + names[0] = re.sub(r"\.py$", "", names[0]) + # Put any params back. + names[-1] += possible_open_bracket + params + return names + + +class LogXML: + def __init__( + self, + logfile, + prefix: str | None, + suite_name: str = "pytest", + logging: str = "no", + report_duration: str = "total", + family="xunit1", + log_passing_tests: bool = True, + ) -> None: + logfile = os.path.expanduser(os.path.expandvars(logfile)) + self.logfile = os.path.normpath(os.path.abspath(logfile)) + self.prefix = prefix + self.suite_name = suite_name + self.logging = logging + self.log_passing_tests = log_passing_tests + self.report_duration = report_duration + self.family = family + self.stats: dict[str, int] = dict.fromkeys( + ["error", "passed", "failure", "skipped"], 0 + ) + self.node_reporters: dict[tuple[str | TestReport, object], _NodeReporter] = {} + self.node_reporters_ordered: list[_NodeReporter] = [] + self.global_properties: list[tuple[str, str]] = [] + + # List of reports that failed on call but teardown is pending. + self.open_reports: list[TestReport] = [] + self.cnt_double_fail_tests = 0 + + # Replaces convenience family with real family. + if self.family == "legacy": + self.family = "xunit1" + + def finalize(self, report: TestReport) -> None: + nodeid = getattr(report, "nodeid", report) + # Local hack to handle xdist report order. + workernode = getattr(report, "node", None) + reporter = self.node_reporters.pop((nodeid, workernode)) + + for propname, propvalue in report.user_properties: + reporter.add_property(propname, str(propvalue)) + + if reporter is not None: + reporter.finalize() + + def node_reporter(self, report: TestReport | str) -> _NodeReporter: + nodeid: str | TestReport = getattr(report, "nodeid", report) + # Local hack to handle xdist report order. + workernode = getattr(report, "node", None) + + key = nodeid, workernode + + if key in self.node_reporters: + # TODO: breaks for --dist=each + return self.node_reporters[key] + + reporter = _NodeReporter(nodeid, self) + + self.node_reporters[key] = reporter + self.node_reporters_ordered.append(reporter) + + return reporter + + def add_stats(self, key: str) -> None: + if key in self.stats: + self.stats[key] += 1 + + def _opentestcase(self, report: TestReport) -> _NodeReporter: + reporter = self.node_reporter(report) + reporter.record_testreport(report) + return reporter + + def pytest_runtest_logreport(self, report: TestReport) -> None: + """Handle a setup/call/teardown report, generating the appropriate + XML tags as necessary. + + Note: due to plugins like xdist, this hook may be called in interlaced + order with reports from other nodes. For example: + + Usual call order: + -> setup node1 + -> call node1 + -> teardown node1 + -> setup node2 + -> call node2 + -> teardown node2 + + Possible call order in xdist: + -> setup node1 + -> call node1 + -> setup node2 + -> call node2 + -> teardown node2 + -> teardown node1 + """ + close_report = None + if report.passed: + if report.when == "call": # ignore setup/teardown + reporter = self._opentestcase(report) + reporter.append_pass(report) + elif report.failed: + if report.when == "teardown": + # The following vars are needed when xdist plugin is used. + report_wid = getattr(report, "worker_id", None) + report_ii = getattr(report, "item_index", None) + close_report = next( + ( + rep + for rep in self.open_reports + if ( + rep.nodeid == report.nodeid + and getattr(rep, "item_index", None) == report_ii + and getattr(rep, "worker_id", None) == report_wid + ) + ), + None, + ) + if close_report: + # We need to open new testcase in case we have failure in + # call and error in teardown in order to follow junit + # schema. + self.finalize(close_report) + self.cnt_double_fail_tests += 1 + reporter = self._opentestcase(report) + if report.when == "call": + reporter.append_failure(report) + self.open_reports.append(report) + if not self.log_passing_tests: + reporter.write_captured_output(report) + else: + reporter.append_error(report) + elif report.skipped: + reporter = self._opentestcase(report) + reporter.append_skipped(report) + self.update_testcase_duration(report) + if report.when == "teardown": + reporter = self._opentestcase(report) + reporter.write_captured_output(report) + + self.finalize(report) + report_wid = getattr(report, "worker_id", None) + report_ii = getattr(report, "item_index", None) + close_report = next( + ( + rep + for rep in self.open_reports + if ( + rep.nodeid == report.nodeid + and getattr(rep, "item_index", None) == report_ii + and getattr(rep, "worker_id", None) == report_wid + ) + ), + None, + ) + if close_report: + self.open_reports.remove(close_report) + + def update_testcase_duration(self, report: TestReport) -> None: + """Accumulate total duration for nodeid from given report and update + the Junit.testcase with the new total if already created.""" + if self.report_duration in {"total", report.when}: + reporter = self.node_reporter(report) + reporter.duration += getattr(report, "duration", 0.0) + + def pytest_collectreport(self, report: TestReport) -> None: + if not report.passed: + reporter = self._opentestcase(report) + if report.failed: + reporter.append_collect_error(report) + else: + reporter.append_collect_skipped(report) + + def pytest_internalerror(self, excrepr: ExceptionRepr) -> None: + reporter = self.node_reporter("internal") + reporter.attrs.update(classname="pytest", name="internal") + reporter._add_simple("error", "internal error", str(excrepr)) + + def pytest_sessionstart(self) -> None: + self.suite_start_time = timing.time() + + def pytest_sessionfinish(self) -> None: + dirname = os.path.dirname(os.path.abspath(self.logfile)) + # exist_ok avoids filesystem race conditions between checking path existence and requesting creation + os.makedirs(dirname, exist_ok=True) + + with open(self.logfile, "w", encoding="utf-8") as logfile: + suite_stop_time = timing.time() + suite_time_delta = suite_stop_time - self.suite_start_time + + numtests = ( + self.stats["passed"] + + self.stats["failure"] + + self.stats["skipped"] + + self.stats["error"] + - self.cnt_double_fail_tests + ) + logfile.write('') + + suite_node = ET.Element( + "testsuite", + name=self.suite_name, + errors=str(self.stats["error"]), + failures=str(self.stats["failure"]), + skipped=str(self.stats["skipped"]), + tests=str(numtests), + time=f"{suite_time_delta:.3f}", + timestamp=datetime.fromtimestamp(self.suite_start_time, timezone.utc) + .astimezone() + .isoformat(), + hostname=platform.node(), + ) + global_properties = self._get_global_properties_node() + if global_properties is not None: + suite_node.append(global_properties) + for node_reporter in self.node_reporters_ordered: + suite_node.append(node_reporter.to_xml()) + testsuites = ET.Element("testsuites") + testsuites.append(suite_node) + logfile.write(ET.tostring(testsuites, encoding="unicode")) + + def pytest_terminal_summary(self, terminalreporter: TerminalReporter) -> None: + terminalreporter.write_sep("-", f"generated xml file: {self.logfile}") + + def add_global_property(self, name: str, value: object) -> None: + __tracebackhide__ = True + _check_record_param_type("name", name) + self.global_properties.append((name, bin_xml_escape(value))) + + def _get_global_properties_node(self) -> ET.Element | None: + """Return a Junit node containing custom properties, if any.""" + if self.global_properties: + properties = ET.Element("properties") + for name, value in self.global_properties: + properties.append(ET.Element("property", name=name, value=value)) + return properties + return None diff --git a/.venv/Lib/site-packages/_pytest/legacypath.py b/.venv/Lib/site-packages/_pytest/legacypath.py new file mode 100644 index 0000000..59e8ef6 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/legacypath.py @@ -0,0 +1,468 @@ +# mypy: allow-untyped-defs +"""Add backward compatibility support for the legacy py path type.""" + +from __future__ import annotations + +import dataclasses +from pathlib import Path +import shlex +import subprocess +from typing import Final +from typing import final +from typing import TYPE_CHECKING + +from iniconfig import SectionWrapper + +from _pytest.cacheprovider import Cache +from _pytest.compat import LEGACY_PATH +from _pytest.compat import legacy_path +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.config import PytestPluginManager +from _pytest.deprecated import check_ispytest +from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureRequest +from _pytest.main import Session +from _pytest.monkeypatch import MonkeyPatch +from _pytest.nodes import Collector +from _pytest.nodes import Item +from _pytest.nodes import Node +from _pytest.pytester import HookRecorder +from _pytest.pytester import Pytester +from _pytest.pytester import RunResult +from _pytest.terminal import TerminalReporter +from _pytest.tmpdir import TempPathFactory + + +if TYPE_CHECKING: + import pexpect + + +@final +class Testdir: + """ + Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead. + + All methods just forward to an internal :class:`Pytester` instance, converting results + to `legacy_path` objects as necessary. + """ + + __test__ = False + + CLOSE_STDIN: Final = Pytester.CLOSE_STDIN + TimeoutExpired: Final = Pytester.TimeoutExpired + + def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: + check_ispytest(_ispytest) + self._pytester = pytester + + @property + def tmpdir(self) -> LEGACY_PATH: + """Temporary directory where tests are executed.""" + return legacy_path(self._pytester.path) + + @property + def test_tmproot(self) -> LEGACY_PATH: + return legacy_path(self._pytester._test_tmproot) + + @property + def request(self): + return self._pytester._request + + @property + def plugins(self): + return self._pytester.plugins + + @plugins.setter + def plugins(self, plugins): + self._pytester.plugins = plugins + + @property + def monkeypatch(self) -> MonkeyPatch: + return self._pytester._monkeypatch + + def make_hook_recorder(self, pluginmanager) -> HookRecorder: + """See :meth:`Pytester.make_hook_recorder`.""" + return self._pytester.make_hook_recorder(pluginmanager) + + def chdir(self) -> None: + """See :meth:`Pytester.chdir`.""" + return self._pytester.chdir() + + def finalize(self) -> None: + return self._pytester._finalize() + + def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH: + """See :meth:`Pytester.makefile`.""" + if ext and not ext.startswith("."): + # pytester.makefile is going to throw a ValueError in a way that + # testdir.makefile did not, because + # pathlib.Path is stricter suffixes than py.path + # This ext arguments is likely user error, but since testdir has + # allowed this, we will prepend "." as a workaround to avoid breaking + # testdir usage that worked before + ext = "." + ext + return legacy_path(self._pytester.makefile(ext, *args, **kwargs)) + + def makeconftest(self, source) -> LEGACY_PATH: + """See :meth:`Pytester.makeconftest`.""" + return legacy_path(self._pytester.makeconftest(source)) + + def makeini(self, source) -> LEGACY_PATH: + """See :meth:`Pytester.makeini`.""" + return legacy_path(self._pytester.makeini(source)) + + def getinicfg(self, source: str) -> SectionWrapper: + """See :meth:`Pytester.getinicfg`.""" + return self._pytester.getinicfg(source) + + def makepyprojecttoml(self, source) -> LEGACY_PATH: + """See :meth:`Pytester.makepyprojecttoml`.""" + return legacy_path(self._pytester.makepyprojecttoml(source)) + + def makepyfile(self, *args, **kwargs) -> LEGACY_PATH: + """See :meth:`Pytester.makepyfile`.""" + return legacy_path(self._pytester.makepyfile(*args, **kwargs)) + + def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH: + """See :meth:`Pytester.maketxtfile`.""" + return legacy_path(self._pytester.maketxtfile(*args, **kwargs)) + + def syspathinsert(self, path=None) -> None: + """See :meth:`Pytester.syspathinsert`.""" + return self._pytester.syspathinsert(path) + + def mkdir(self, name) -> LEGACY_PATH: + """See :meth:`Pytester.mkdir`.""" + return legacy_path(self._pytester.mkdir(name)) + + def mkpydir(self, name) -> LEGACY_PATH: + """See :meth:`Pytester.mkpydir`.""" + return legacy_path(self._pytester.mkpydir(name)) + + def copy_example(self, name=None) -> LEGACY_PATH: + """See :meth:`Pytester.copy_example`.""" + return legacy_path(self._pytester.copy_example(name)) + + def getnode(self, config: Config, arg) -> Item | Collector | None: + """See :meth:`Pytester.getnode`.""" + return self._pytester.getnode(config, arg) + + def getpathnode(self, path): + """See :meth:`Pytester.getpathnode`.""" + return self._pytester.getpathnode(path) + + def genitems(self, colitems: list[Item | Collector]) -> list[Item]: + """See :meth:`Pytester.genitems`.""" + return self._pytester.genitems(colitems) + + def runitem(self, source): + """See :meth:`Pytester.runitem`.""" + return self._pytester.runitem(source) + + def inline_runsource(self, source, *cmdlineargs): + """See :meth:`Pytester.inline_runsource`.""" + return self._pytester.inline_runsource(source, *cmdlineargs) + + def inline_genitems(self, *args): + """See :meth:`Pytester.inline_genitems`.""" + return self._pytester.inline_genitems(*args) + + def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False): + """See :meth:`Pytester.inline_run`.""" + return self._pytester.inline_run( + *args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc + ) + + def runpytest_inprocess(self, *args, **kwargs) -> RunResult: + """See :meth:`Pytester.runpytest_inprocess`.""" + return self._pytester.runpytest_inprocess(*args, **kwargs) + + def runpytest(self, *args, **kwargs) -> RunResult: + """See :meth:`Pytester.runpytest`.""" + return self._pytester.runpytest(*args, **kwargs) + + def parseconfig(self, *args) -> Config: + """See :meth:`Pytester.parseconfig`.""" + return self._pytester.parseconfig(*args) + + def parseconfigure(self, *args) -> Config: + """See :meth:`Pytester.parseconfigure`.""" + return self._pytester.parseconfigure(*args) + + def getitem(self, source, funcname="test_func"): + """See :meth:`Pytester.getitem`.""" + return self._pytester.getitem(source, funcname) + + def getitems(self, source): + """See :meth:`Pytester.getitems`.""" + return self._pytester.getitems(source) + + def getmodulecol(self, source, configargs=(), withinit=False): + """See :meth:`Pytester.getmodulecol`.""" + return self._pytester.getmodulecol( + source, configargs=configargs, withinit=withinit + ) + + def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: + """See :meth:`Pytester.collect_by_name`.""" + return self._pytester.collect_by_name(modcol, name) + + def popen( + self, + cmdargs, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=CLOSE_STDIN, + **kw, + ): + """See :meth:`Pytester.popen`.""" + return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw) + + def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult: + """See :meth:`Pytester.run`.""" + return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin) + + def runpython(self, script) -> RunResult: + """See :meth:`Pytester.runpython`.""" + return self._pytester.runpython(script) + + def runpython_c(self, command): + """See :meth:`Pytester.runpython_c`.""" + return self._pytester.runpython_c(command) + + def runpytest_subprocess(self, *args, timeout=None) -> RunResult: + """See :meth:`Pytester.runpytest_subprocess`.""" + return self._pytester.runpytest_subprocess(*args, timeout=timeout) + + def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: + """See :meth:`Pytester.spawn_pytest`.""" + return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout) + + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: + """See :meth:`Pytester.spawn`.""" + return self._pytester.spawn(cmd, expect_timeout=expect_timeout) + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return str(self.tmpdir) + + +class LegacyTestdirPlugin: + @staticmethod + @fixture + def testdir(pytester: Pytester) -> Testdir: + """ + Identical to :fixture:`pytester`, and provides an instance whose methods return + legacy ``LEGACY_PATH`` objects instead when applicable. + + New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`. + """ + return Testdir(pytester, _ispytest=True) + + +@final +@dataclasses.dataclass +class TempdirFactory: + """Backward compatibility wrapper that implements ``py.path.local`` + for :class:`TempPathFactory`. + + .. note:: + These days, it is preferred to use ``tmp_path_factory``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + + """ + + _tmppath_factory: TempPathFactory + + def __init__( + self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False + ) -> None: + check_ispytest(_ispytest) + self._tmppath_factory = tmppath_factory + + def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: + """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object.""" + return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) + + def getbasetemp(self) -> LEGACY_PATH: + """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object.""" + return legacy_path(self._tmppath_factory.getbasetemp().resolve()) + + +class LegacyTmpdirPlugin: + @staticmethod + @fixture(scope="session") + def tmpdir_factory(request: FixtureRequest) -> TempdirFactory: + """Return a :class:`pytest.TempdirFactory` instance for the test session.""" + # Set dynamically by pytest_configure(). + return request.config._tmpdirhandler # type: ignore + + @staticmethod + @fixture + def tmpdir(tmp_path: Path) -> LEGACY_PATH: + """Return a temporary directory (as `legacy_path`_ object) + which is unique to each test function invocation. + The temporary directory is created as a subdirectory + of the base temporary directory, with configurable retention, + as discussed in :ref:`temporary directory location and retention`. + + .. note:: + These days, it is preferred to use ``tmp_path``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html + """ + return legacy_path(tmp_path) + + +def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH: + """Return a directory path object with the given name. + + Same as :func:`mkdir`, but returns a legacy py path instance. + """ + return legacy_path(self.mkdir(name)) + + +def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH: + """(deprecated) The file system path of the test module which collected this test.""" + return legacy_path(self.path) + + +def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH: + """The directory from which pytest was invoked. + + Prefer to use ``startpath`` which is a :class:`pathlib.Path`. + + :type: LEGACY_PATH + """ + return legacy_path(self.startpath) + + +def Config_invocation_dir(self: Config) -> LEGACY_PATH: + """The directory from which pytest was invoked. + + Prefer to use :attr:`invocation_params.dir `, + which is a :class:`pathlib.Path`. + + :type: LEGACY_PATH + """ + return legacy_path(str(self.invocation_params.dir)) + + +def Config_rootdir(self: Config) -> LEGACY_PATH: + """The path to the :ref:`rootdir `. + + Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`. + + :type: LEGACY_PATH + """ + return legacy_path(str(self.rootpath)) + + +def Config_inifile(self: Config) -> LEGACY_PATH | None: + """The path to the :ref:`configfile `. + + Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. + + :type: Optional[LEGACY_PATH] + """ + return legacy_path(str(self.inipath)) if self.inipath else None + + +def Session_startdir(self: Session) -> LEGACY_PATH: + """The path from which pytest was invoked. + + Prefer to use ``startpath`` which is a :class:`pathlib.Path`. + + :type: LEGACY_PATH + """ + return legacy_path(self.startpath) + + +def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]): + if type == "pathlist": + # TODO: This assert is probably not valid in all cases. + assert self.inipath is not None + dp = self.inipath.parent + input_values = shlex.split(value) if isinstance(value, str) else value + return [legacy_path(str(dp / x)) for x in input_values] + else: + raise ValueError(f"unknown configuration type: {type}", value) + + +def Node_fspath(self: Node) -> LEGACY_PATH: + """(deprecated) returns a legacy_path copy of self.path""" + return legacy_path(self.path) + + +def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None: + self.path = Path(value) + + +@hookimpl(tryfirst=True) +def pytest_load_initial_conftests(early_config: Config) -> None: + """Monkeypatch legacy path attributes in several classes, as early as possible.""" + mp = MonkeyPatch() + early_config.add_cleanup(mp.undo) + + # Add Cache.makedir(). + mp.setattr(Cache, "makedir", Cache_makedir, raising=False) + + # Add FixtureRequest.fspath property. + mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False) + + # Add TerminalReporter.startdir property. + mp.setattr( + TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False + ) + + # Add Config.{invocation_dir,rootdir,inifile} properties. + mp.setattr(Config, "invocation_dir", property(Config_invocation_dir), raising=False) + mp.setattr(Config, "rootdir", property(Config_rootdir), raising=False) + mp.setattr(Config, "inifile", property(Config_inifile), raising=False) + + # Add Session.startdir property. + mp.setattr(Session, "startdir", property(Session_startdir), raising=False) + + # Add pathlist configuration type. + mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type) + + # Add Node.fspath property. + mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False) + + +@hookimpl +def pytest_configure(config: Config) -> None: + """Installs the LegacyTmpdirPlugin if the ``tmpdir`` plugin is also installed.""" + if config.pluginmanager.has_plugin("tmpdir"): + mp = MonkeyPatch() + config.add_cleanup(mp.undo) + # Create TmpdirFactory and attach it to the config object. + # + # This is to comply with existing plugins which expect the handler to be + # available at pytest_configure time, but ideally should be moved entirely + # to the tmpdir_factory session fixture. + try: + tmp_path_factory = config._tmp_path_factory # type: ignore[attr-defined] + except AttributeError: + # tmpdir plugin is blocked. + pass + else: + _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True) + mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False) + + config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir") + + +@hookimpl +def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None: + # pytester is not loaded by default and is commonly loaded from a conftest, + # so checking for it in `pytest_configure` is not enough. + is_pytester = plugin is manager.get_plugin("pytester") + if is_pytester and not manager.is_registered(LegacyTestdirPlugin): + manager.register(LegacyTestdirPlugin, "legacypath-pytester") diff --git a/.venv/Lib/site-packages/_pytest/logging.py b/.venv/Lib/site-packages/_pytest/logging.py new file mode 100644 index 0000000..08c826f --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/logging.py @@ -0,0 +1,955 @@ +# mypy: allow-untyped-defs +"""Access and control log capturing.""" + +from __future__ import annotations + +from contextlib import contextmanager +from contextlib import nullcontext +from datetime import datetime +from datetime import timedelta +from datetime import timezone +import io +from io import StringIO +import logging +from logging import LogRecord +import os +from pathlib import Path +import re +from types import TracebackType +from typing import AbstractSet +from typing import Dict +from typing import final +from typing import Generator +from typing import Generic +from typing import List +from typing import Literal +from typing import Mapping +from typing import TYPE_CHECKING +from typing import TypeVar + +from _pytest import nodes +from _pytest._io import TerminalWriter +from _pytest.capture import CaptureManager +from _pytest.config import _strtobool +from _pytest.config import Config +from _pytest.config import create_terminal_writer +from _pytest.config import hookimpl +from _pytest.config import UsageError +from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest +from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureRequest +from _pytest.main import Session +from _pytest.stash import StashKey +from _pytest.terminal import TerminalReporter + + +if TYPE_CHECKING: + logging_StreamHandler = logging.StreamHandler[StringIO] +else: + logging_StreamHandler = logging.StreamHandler + +DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s" +DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S" +_ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m") +caplog_handler_key = StashKey["LogCaptureHandler"]() +caplog_records_key = StashKey[Dict[str, List[logging.LogRecord]]]() + + +def _remove_ansi_escape_sequences(text: str) -> str: + return _ANSI_ESCAPE_SEQ.sub("", text) + + +class DatetimeFormatter(logging.Formatter): + """A logging formatter which formats record with + :func:`datetime.datetime.strftime` formatter instead of + :func:`time.strftime` in case of microseconds in format string. + """ + + def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: + if datefmt and "%f" in datefmt: + ct = self.converter(record.created) + tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) + # Construct `datetime.datetime` object from `struct_time` + # and msecs information from `record` + # Using int() instead of round() to avoid it exceeding 1_000_000 and causing a ValueError (#11861). + dt = datetime(*ct[0:6], microsecond=int(record.msecs * 1000), tzinfo=tz) + return dt.strftime(datefmt) + # Use `logging.Formatter` for non-microsecond formats + return super().formatTime(record, datefmt) + + +class ColoredLevelFormatter(DatetimeFormatter): + """A logging formatter which colorizes the %(levelname)..s part of the + log format passed to __init__.""" + + LOGLEVEL_COLOROPTS: Mapping[int, AbstractSet[str]] = { + logging.CRITICAL: {"red"}, + logging.ERROR: {"red", "bold"}, + logging.WARNING: {"yellow"}, + logging.WARN: {"yellow"}, + logging.INFO: {"green"}, + logging.DEBUG: {"purple"}, + logging.NOTSET: set(), + } + LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-.]?\d*(?:\.\d+)?s)") + + def __init__(self, terminalwriter: TerminalWriter, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._terminalwriter = terminalwriter + self._original_fmt = self._style._fmt + self._level_to_fmt_mapping: dict[int, str] = {} + + for level, color_opts in self.LOGLEVEL_COLOROPTS.items(): + self.add_color_level(level, *color_opts) + + def add_color_level(self, level: int, *color_opts: str) -> None: + """Add or update color opts for a log level. + + :param level: + Log level to apply a style to, e.g. ``logging.INFO``. + :param color_opts: + ANSI escape sequence color options. Capitalized colors indicates + background color, i.e. ``'green', 'Yellow', 'bold'`` will give bold + green text on yellow background. + + .. warning:: + This is an experimental API. + """ + assert self._fmt is not None + levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt) + if not levelname_fmt_match: + return + levelname_fmt = levelname_fmt_match.group() + + formatted_levelname = levelname_fmt % {"levelname": logging.getLevelName(level)} + + # add ANSI escape sequences around the formatted levelname + color_kwargs = {name: True for name in color_opts} + colorized_formatted_levelname = self._terminalwriter.markup( + formatted_levelname, **color_kwargs + ) + self._level_to_fmt_mapping[level] = self.LEVELNAME_FMT_REGEX.sub( + colorized_formatted_levelname, self._fmt + ) + + def format(self, record: logging.LogRecord) -> str: + fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt) + self._style._fmt = fmt + return super().format(record) + + +class PercentStyleMultiline(logging.PercentStyle): + """A logging style with special support for multiline messages. + + If the message of a record consists of multiple lines, this style + formats the message as if each line were logged separately. + """ + + def __init__(self, fmt: str, auto_indent: int | str | bool | None) -> None: + super().__init__(fmt) + self._auto_indent = self._get_auto_indent(auto_indent) + + @staticmethod + def _get_auto_indent(auto_indent_option: int | str | bool | None) -> int: + """Determine the current auto indentation setting. + + Specify auto indent behavior (on/off/fixed) by passing in + extra={"auto_indent": [value]} to the call to logging.log() or + using a --log-auto-indent [value] command line or the + log_auto_indent [value] config option. + + Default behavior is auto-indent off. + + Using the string "True" or "on" or the boolean True as the value + turns auto indent on, using the string "False" or "off" or the + boolean False or the int 0 turns it off, and specifying a + positive integer fixes the indentation position to the value + specified. + + Any other values for the option are invalid, and will silently be + converted to the default. + + :param None|bool|int|str auto_indent_option: + User specified option for indentation from command line, config + or extra kwarg. Accepts int, bool or str. str option accepts the + same range of values as boolean config options, as well as + positive integers represented in str form. + + :returns: + Indentation value, which can be + -1 (automatically determine indentation) or + 0 (auto-indent turned off) or + >0 (explicitly set indentation position). + """ + if auto_indent_option is None: + return 0 + elif isinstance(auto_indent_option, bool): + if auto_indent_option: + return -1 + else: + return 0 + elif isinstance(auto_indent_option, int): + return int(auto_indent_option) + elif isinstance(auto_indent_option, str): + try: + return int(auto_indent_option) + except ValueError: + pass + try: + if _strtobool(auto_indent_option): + return -1 + except ValueError: + return 0 + + return 0 + + def format(self, record: logging.LogRecord) -> str: + if "\n" in record.message: + if hasattr(record, "auto_indent"): + # Passed in from the "extra={}" kwarg on the call to logging.log(). + auto_indent = self._get_auto_indent(record.auto_indent) + else: + auto_indent = self._auto_indent + + if auto_indent: + lines = record.message.splitlines() + formatted = self._fmt % {**record.__dict__, "message": lines[0]} + + if auto_indent < 0: + indentation = _remove_ansi_escape_sequences(formatted).find( + lines[0] + ) + else: + # Optimizes logging by allowing a fixed indentation. + indentation = auto_indent + lines[0] = formatted + return ("\n" + " " * indentation).join(lines) + return self._fmt % record.__dict__ + + +def get_option_ini(config: Config, *names: str): + for name in names: + ret = config.getoption(name) # 'default' arg won't work as expected + if ret is None: + ret = config.getini(name) + if ret: + return ret + + +def pytest_addoption(parser: Parser) -> None: + """Add options to control log capturing.""" + group = parser.getgroup("logging") + + def add_option_ini(option, dest, default=None, type=None, **kwargs): + parser.addini( + dest, default=default, type=type, help="Default value for " + option + ) + group.addoption(option, dest=dest, **kwargs) + + add_option_ini( + "--log-level", + dest="log_level", + default=None, + metavar="LEVEL", + help=( + "Level of messages to catch/display." + " Not set by default, so it depends on the root/parent log handler's" + ' effective level, where it is "WARNING" by default.' + ), + ) + add_option_ini( + "--log-format", + dest="log_format", + default=DEFAULT_LOG_FORMAT, + help="Log format used by the logging module", + ) + add_option_ini( + "--log-date-format", + dest="log_date_format", + default=DEFAULT_LOG_DATE_FORMAT, + help="Log date format used by the logging module", + ) + parser.addini( + "log_cli", + default=False, + type="bool", + help='Enable log display during test run (also known as "live logging")', + ) + add_option_ini( + "--log-cli-level", dest="log_cli_level", default=None, help="CLI logging level" + ) + add_option_ini( + "--log-cli-format", + dest="log_cli_format", + default=None, + help="Log format used by the logging module", + ) + add_option_ini( + "--log-cli-date-format", + dest="log_cli_date_format", + default=None, + help="Log date format used by the logging module", + ) + add_option_ini( + "--log-file", + dest="log_file", + default=None, + help="Path to a file when logging will be written to", + ) + add_option_ini( + "--log-file-mode", + dest="log_file_mode", + default="w", + choices=["w", "a"], + help="Log file open mode", + ) + add_option_ini( + "--log-file-level", + dest="log_file_level", + default=None, + help="Log file logging level", + ) + add_option_ini( + "--log-file-format", + dest="log_file_format", + default=None, + help="Log format used by the logging module", + ) + add_option_ini( + "--log-file-date-format", + dest="log_file_date_format", + default=None, + help="Log date format used by the logging module", + ) + add_option_ini( + "--log-auto-indent", + dest="log_auto_indent", + default=None, + help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.", + ) + group.addoption( + "--log-disable", + action="append", + default=[], + dest="logger_disable", + help="Disable a logger by name. Can be passed multiple times.", + ) + + +_HandlerType = TypeVar("_HandlerType", bound=logging.Handler) + + +# Not using @contextmanager for performance reasons. +class catching_logs(Generic[_HandlerType]): + """Context manager that prepares the whole logging machinery properly.""" + + __slots__ = ("handler", "level", "orig_level") + + def __init__(self, handler: _HandlerType, level: int | None = None) -> None: + self.handler = handler + self.level = level + + def __enter__(self) -> _HandlerType: + root_logger = logging.getLogger() + if self.level is not None: + self.handler.setLevel(self.level) + root_logger.addHandler(self.handler) + if self.level is not None: + self.orig_level = root_logger.level + root_logger.setLevel(min(self.orig_level, self.level)) + return self.handler + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + root_logger = logging.getLogger() + if self.level is not None: + root_logger.setLevel(self.orig_level) + root_logger.removeHandler(self.handler) + + +class LogCaptureHandler(logging_StreamHandler): + """A logging handler that stores log records and the log text.""" + + def __init__(self) -> None: + """Create a new log handler.""" + super().__init__(StringIO()) + self.records: list[logging.LogRecord] = [] + + def emit(self, record: logging.LogRecord) -> None: + """Keep the log records in a list in addition to the log text.""" + self.records.append(record) + super().emit(record) + + def reset(self) -> None: + self.records = [] + self.stream = StringIO() + + def clear(self) -> None: + self.records.clear() + self.stream = StringIO() + + def handleError(self, record: logging.LogRecord) -> None: + if logging.raiseExceptions: + # Fail the test if the log message is bad (emit failed). + # The default behavior of logging is to print "Logging error" + # to stderr with the call stack and some extra details. + # pytest wants to make such mistakes visible during testing. + raise # noqa: PLE0704 + + +@final +class LogCaptureFixture: + """Provides access and control of log capturing.""" + + def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: + check_ispytest(_ispytest) + self._item = item + self._initial_handler_level: int | None = None + # Dict of log name -> log level. + self._initial_logger_levels: dict[str | None, int] = {} + self._initial_disabled_logging_level: int | None = None + + def _finalize(self) -> None: + """Finalize the fixture. + + This restores the log levels and the disabled logging levels changed by :meth:`set_level`. + """ + # Restore log levels. + if self._initial_handler_level is not None: + self.handler.setLevel(self._initial_handler_level) + for logger_name, level in self._initial_logger_levels.items(): + logger = logging.getLogger(logger_name) + logger.setLevel(level) + # Disable logging at the original disabled logging level. + if self._initial_disabled_logging_level is not None: + logging.disable(self._initial_disabled_logging_level) + self._initial_disabled_logging_level = None + + @property + def handler(self) -> LogCaptureHandler: + """Get the logging handler used by the fixture.""" + return self._item.stash[caplog_handler_key] + + def get_records( + self, when: Literal["setup", "call", "teardown"] + ) -> list[logging.LogRecord]: + """Get the logging records for one of the possible test phases. + + :param when: + Which test phase to obtain the records from. + Valid values are: "setup", "call" and "teardown". + + :returns: The list of captured records at the given stage. + + .. versionadded:: 3.4 + """ + return self._item.stash[caplog_records_key].get(when, []) + + @property + def text(self) -> str: + """The formatted log text.""" + return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) + + @property + def records(self) -> list[logging.LogRecord]: + """The list of log records.""" + return self.handler.records + + @property + def record_tuples(self) -> list[tuple[str, int, str]]: + """A list of a stripped down version of log records intended + for use in assertion comparison. + + The format of the tuple is: + + (logger_name, log_level, message) + """ + return [(r.name, r.levelno, r.getMessage()) for r in self.records] + + @property + def messages(self) -> list[str]: + """A list of format-interpolated log messages. + + Unlike 'records', which contains the format string and parameters for + interpolation, log messages in this list are all interpolated. + + Unlike 'text', which contains the output from the handler, log + messages in this list are unadorned with levels, timestamps, etc, + making exact comparisons more reliable. + + Note that traceback or stack info (from :func:`logging.exception` or + the `exc_info` or `stack_info` arguments to the logging functions) is + not included, as this is added by the formatter in the handler. + + .. versionadded:: 3.7 + """ + return [r.getMessage() for r in self.records] + + def clear(self) -> None: + """Reset the list of log records and the captured log text.""" + self.handler.clear() + + def _force_enable_logging( + self, level: int | str, logger_obj: logging.Logger + ) -> int: + """Enable the desired logging level if the global level was disabled via ``logging.disabled``. + + Only enables logging levels greater than or equal to the requested ``level``. + + Does nothing if the desired ``level`` wasn't disabled. + + :param level: + The logger level caplog should capture. + All logging is enabled if a non-standard logging level string is supplied. + Valid level strings are in :data:`logging._nameToLevel`. + :param logger_obj: The logger object to check. + + :return: The original disabled logging level. + """ + original_disable_level: int = logger_obj.manager.disable + + if isinstance(level, str): + # Try to translate the level string to an int for `logging.disable()` + level = logging.getLevelName(level) + + if not isinstance(level, int): + # The level provided was not valid, so just un-disable all logging. + logging.disable(logging.NOTSET) + elif not logger_obj.isEnabledFor(level): + # Each level is `10` away from other levels. + # https://docs.python.org/3/library/logging.html#logging-levels + disable_level = max(level - 10, logging.NOTSET) + logging.disable(disable_level) + + return original_disable_level + + def set_level(self, level: int | str, logger: str | None = None) -> None: + """Set the threshold level of a logger for the duration of a test. + + Logging messages which are less severe than this level will not be captured. + + .. versionchanged:: 3.4 + The levels of the loggers changed by this function will be + restored to their initial values at the end of the test. + + Will enable the requested logging level if it was disabled via :func:`logging.disable`. + + :param level: The level. + :param logger: The logger to update. If not given, the root logger. + """ + logger_obj = logging.getLogger(logger) + # Save the original log-level to restore it during teardown. + self._initial_logger_levels.setdefault(logger, logger_obj.level) + logger_obj.setLevel(level) + if self._initial_handler_level is None: + self._initial_handler_level = self.handler.level + self.handler.setLevel(level) + initial_disabled_logging_level = self._force_enable_logging(level, logger_obj) + if self._initial_disabled_logging_level is None: + self._initial_disabled_logging_level = initial_disabled_logging_level + + @contextmanager + def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]: + """Context manager that sets the level for capturing of logs. After + the end of the 'with' statement the level is restored to its original + value. + + Will enable the requested logging level if it was disabled via :func:`logging.disable`. + + :param level: The level. + :param logger: The logger to update. If not given, the root logger. + """ + logger_obj = logging.getLogger(logger) + orig_level = logger_obj.level + logger_obj.setLevel(level) + handler_orig_level = self.handler.level + self.handler.setLevel(level) + original_disable_level = self._force_enable_logging(level, logger_obj) + try: + yield + finally: + logger_obj.setLevel(orig_level) + self.handler.setLevel(handler_orig_level) + logging.disable(original_disable_level) + + @contextmanager + def filtering(self, filter_: logging.Filter) -> Generator[None]: + """Context manager that temporarily adds the given filter to the caplog's + :meth:`handler` for the 'with' statement block, and removes that filter at the + end of the block. + + :param filter_: A custom :class:`logging.Filter` object. + + .. versionadded:: 7.5 + """ + self.handler.addFilter(filter_) + try: + yield + finally: + self.handler.removeFilter(filter_) + + +@fixture +def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture]: + """Access and control log capturing. + + Captured logs are available through the following properties/methods:: + + * caplog.messages -> list of format-interpolated log messages + * caplog.text -> string containing formatted log output + * caplog.records -> list of logging.LogRecord instances + * caplog.record_tuples -> list of (logger_name, level, message) tuples + * caplog.clear() -> clear captured records and formatted log output string + """ + result = LogCaptureFixture(request.node, _ispytest=True) + yield result + result._finalize() + + +def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None: + for setting_name in setting_names: + log_level = config.getoption(setting_name) + if log_level is None: + log_level = config.getini(setting_name) + if log_level: + break + else: + return None + + if isinstance(log_level, str): + log_level = log_level.upper() + try: + return int(getattr(logging, log_level, log_level)) + except ValueError as e: + # Python logging does not recognise this as a logging level + raise UsageError( + f"'{log_level}' is not recognized as a logging level name for " + f"'{setting_name}'. Please consider passing the " + "logging level num instead." + ) from e + + +# run after terminalreporter/capturemanager are configured +@hookimpl(trylast=True) +def pytest_configure(config: Config) -> None: + config.pluginmanager.register(LoggingPlugin(config), "logging-plugin") + + +class LoggingPlugin: + """Attaches to the logging module and captures log messages for each test.""" + + def __init__(self, config: Config) -> None: + """Create a new plugin to capture log messages. + + The formatter can be safely shared across all handlers so + create a single one for the entire test session here. + """ + self._config = config + + # Report logging. + self.formatter = self._create_formatter( + get_option_ini(config, "log_format"), + get_option_ini(config, "log_date_format"), + get_option_ini(config, "log_auto_indent"), + ) + self.log_level = get_log_level_for_setting(config, "log_level") + self.caplog_handler = LogCaptureHandler() + self.caplog_handler.setFormatter(self.formatter) + self.report_handler = LogCaptureHandler() + self.report_handler.setFormatter(self.formatter) + + # File logging. + self.log_file_level = get_log_level_for_setting( + config, "log_file_level", "log_level" + ) + log_file = get_option_ini(config, "log_file") or os.devnull + if log_file != os.devnull: + directory = os.path.dirname(os.path.abspath(log_file)) + if not os.path.isdir(directory): + os.makedirs(directory) + + self.log_file_mode = get_option_ini(config, "log_file_mode") or "w" + self.log_file_handler = _FileHandler( + log_file, mode=self.log_file_mode, encoding="UTF-8" + ) + log_file_format = get_option_ini(config, "log_file_format", "log_format") + log_file_date_format = get_option_ini( + config, "log_file_date_format", "log_date_format" + ) + + log_file_formatter = DatetimeFormatter( + log_file_format, datefmt=log_file_date_format + ) + self.log_file_handler.setFormatter(log_file_formatter) + + # CLI/live logging. + self.log_cli_level = get_log_level_for_setting( + config, "log_cli_level", "log_level" + ) + if self._log_cli_enabled(): + terminal_reporter = config.pluginmanager.get_plugin("terminalreporter") + # Guaranteed by `_log_cli_enabled()`. + assert terminal_reporter is not None + capture_manager = config.pluginmanager.get_plugin("capturemanager") + # if capturemanager plugin is disabled, live logging still works. + self.log_cli_handler: ( + _LiveLoggingStreamHandler | _LiveLoggingNullHandler + ) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) + else: + self.log_cli_handler = _LiveLoggingNullHandler() + log_cli_formatter = self._create_formatter( + get_option_ini(config, "log_cli_format", "log_format"), + get_option_ini(config, "log_cli_date_format", "log_date_format"), + get_option_ini(config, "log_auto_indent"), + ) + self.log_cli_handler.setFormatter(log_cli_formatter) + self._disable_loggers(loggers_to_disable=config.option.logger_disable) + + def _disable_loggers(self, loggers_to_disable: list[str]) -> None: + if not loggers_to_disable: + return + + for name in loggers_to_disable: + logger = logging.getLogger(name) + logger.disabled = True + + def _create_formatter(self, log_format, log_date_format, auto_indent): + # Color option doesn't exist if terminal plugin is disabled. + color = getattr(self._config.option, "color", "no") + if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search( + log_format + ): + formatter: logging.Formatter = ColoredLevelFormatter( + create_terminal_writer(self._config), log_format, log_date_format + ) + else: + formatter = DatetimeFormatter(log_format, log_date_format) + + formatter._style = PercentStyleMultiline( + formatter._style._fmt, auto_indent=auto_indent + ) + + return formatter + + def set_log_path(self, fname: str) -> None: + """Set the filename parameter for Logging.FileHandler(). + + Creates parent directory if it does not exist. + + .. warning:: + This is an experimental API. + """ + fpath = Path(fname) + + if not fpath.is_absolute(): + fpath = self._config.rootpath / fpath + + if not fpath.parent.exists(): + fpath.parent.mkdir(exist_ok=True, parents=True) + + # https://github.com/python/mypy/issues/11193 + stream: io.TextIOWrapper = fpath.open(mode=self.log_file_mode, encoding="UTF-8") # type: ignore[assignment] + old_stream = self.log_file_handler.setStream(stream) + if old_stream: + old_stream.close() + + def _log_cli_enabled(self) -> bool: + """Return whether live logging is enabled.""" + enabled = self._config.getoption( + "--log-cli-level" + ) is not None or self._config.getini("log_cli") + if not enabled: + return False + + terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter") + if terminal_reporter is None: + # terminal reporter is disabled e.g. by pytest-xdist. + return False + + return True + + @hookimpl(wrapper=True, tryfirst=True) + def pytest_sessionstart(self) -> Generator[None]: + self.log_cli_handler.set_when("sessionstart") + + with catching_logs(self.log_cli_handler, level=self.log_cli_level): + with catching_logs(self.log_file_handler, level=self.log_file_level): + return (yield) + + @hookimpl(wrapper=True, tryfirst=True) + def pytest_collection(self) -> Generator[None]: + self.log_cli_handler.set_when("collection") + + with catching_logs(self.log_cli_handler, level=self.log_cli_level): + with catching_logs(self.log_file_handler, level=self.log_file_level): + return (yield) + + @hookimpl(wrapper=True) + def pytest_runtestloop(self, session: Session) -> Generator[None, object, object]: + if session.config.option.collectonly: + return (yield) + + if self._log_cli_enabled() and self._config.get_verbosity() < 1: + # The verbose flag is needed to avoid messy test progress output. + self._config.option.verbose = 1 + + with catching_logs(self.log_cli_handler, level=self.log_cli_level): + with catching_logs(self.log_file_handler, level=self.log_file_level): + return (yield) # Run all the tests. + + @hookimpl + def pytest_runtest_logstart(self) -> None: + self.log_cli_handler.reset() + self.log_cli_handler.set_when("start") + + @hookimpl + def pytest_runtest_logreport(self) -> None: + self.log_cli_handler.set_when("logreport") + + def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None]: + """Implement the internals of the pytest_runtest_xxx() hooks.""" + with catching_logs( + self.caplog_handler, + level=self.log_level, + ) as caplog_handler, catching_logs( + self.report_handler, + level=self.log_level, + ) as report_handler: + caplog_handler.reset() + report_handler.reset() + item.stash[caplog_records_key][when] = caplog_handler.records + item.stash[caplog_handler_key] = caplog_handler + + try: + yield + finally: + log = report_handler.stream.getvalue().strip() + item.add_report_section(when, "log", log) + + @hookimpl(wrapper=True) + def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None]: + self.log_cli_handler.set_when("setup") + + empty: dict[str, list[logging.LogRecord]] = {} + item.stash[caplog_records_key] = empty + yield from self._runtest_for(item, "setup") + + @hookimpl(wrapper=True) + def pytest_runtest_call(self, item: nodes.Item) -> Generator[None]: + self.log_cli_handler.set_when("call") + + yield from self._runtest_for(item, "call") + + @hookimpl(wrapper=True) + def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None]: + self.log_cli_handler.set_when("teardown") + + try: + yield from self._runtest_for(item, "teardown") + finally: + del item.stash[caplog_records_key] + del item.stash[caplog_handler_key] + + @hookimpl + def pytest_runtest_logfinish(self) -> None: + self.log_cli_handler.set_when("finish") + + @hookimpl(wrapper=True, tryfirst=True) + def pytest_sessionfinish(self) -> Generator[None]: + self.log_cli_handler.set_when("sessionfinish") + + with catching_logs(self.log_cli_handler, level=self.log_cli_level): + with catching_logs(self.log_file_handler, level=self.log_file_level): + return (yield) + + @hookimpl + def pytest_unconfigure(self) -> None: + # Close the FileHandler explicitly. + # (logging.shutdown might have lost the weakref?!) + self.log_file_handler.close() + + +class _FileHandler(logging.FileHandler): + """A logging FileHandler with pytest tweaks.""" + + def handleError(self, record: logging.LogRecord) -> None: + # Handled by LogCaptureHandler. + pass + + +class _LiveLoggingStreamHandler(logging_StreamHandler): + """A logging StreamHandler used by the live logging feature: it will + write a newline before the first log message in each test. + + During live logging we must also explicitly disable stdout/stderr + capturing otherwise it will get captured and won't appear in the + terminal. + """ + + # Officially stream needs to be a IO[str], but TerminalReporter + # isn't. So force it. + stream: TerminalReporter = None # type: ignore + + def __init__( + self, + terminal_reporter: TerminalReporter, + capture_manager: CaptureManager | None, + ) -> None: + super().__init__(stream=terminal_reporter) # type: ignore[arg-type] + self.capture_manager = capture_manager + self.reset() + self.set_when(None) + self._test_outcome_written = False + + def reset(self) -> None: + """Reset the handler; should be called before the start of each test.""" + self._first_record_emitted = False + + def set_when(self, when: str | None) -> None: + """Prepare for the given test phase (setup/call/teardown).""" + self._when = when + self._section_name_shown = False + if when == "start": + self._test_outcome_written = False + + def emit(self, record: logging.LogRecord) -> None: + ctx_manager = ( + self.capture_manager.global_and_fixture_disabled() + if self.capture_manager + else nullcontext() + ) + with ctx_manager: + if not self._first_record_emitted: + self.stream.write("\n") + self._first_record_emitted = True + elif self._when in ("teardown", "finish"): + if not self._test_outcome_written: + self._test_outcome_written = True + self.stream.write("\n") + if not self._section_name_shown and self._when: + self.stream.section("live log " + self._when, sep="-", bold=True) + self._section_name_shown = True + super().emit(record) + + def handleError(self, record: logging.LogRecord) -> None: + # Handled by LogCaptureHandler. + pass + + +class _LiveLoggingNullHandler(logging.NullHandler): + """A logging handler used when live logging is disabled.""" + + def reset(self) -> None: + pass + + def set_when(self, when: str) -> None: + pass + + def handleError(self, record: logging.LogRecord) -> None: + # Handled by LogCaptureHandler. + pass diff --git a/.venv/Lib/site-packages/_pytest/main.py b/.venv/Lib/site-packages/_pytest/main.py new file mode 100644 index 0000000..e5534e9 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/main.py @@ -0,0 +1,1072 @@ +"""Core implementation of the testing process: init, session, runtest loop.""" + +from __future__ import annotations + +import argparse +import dataclasses +import fnmatch +import functools +import importlib +import importlib.util +import os +from pathlib import Path +import sys +from typing import AbstractSet +from typing import Callable +from typing import Dict +from typing import final +from typing import Iterable +from typing import Iterator +from typing import Literal +from typing import overload +from typing import Sequence +from typing import TYPE_CHECKING +import warnings + +import pluggy + +from _pytest import nodes +import _pytest._code +from _pytest.config import Config +from _pytest.config import directory_arg +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config import PytestPluginManager +from _pytest.config import UsageError +from _pytest.config.argparsing import Parser +from _pytest.config.compat import PathAwareHookProxy +from _pytest.outcomes import exit +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import fnmatch_ex +from _pytest.pathlib import safe_exists +from _pytest.pathlib import scandir +from _pytest.reports import CollectReport +from _pytest.reports import TestReport +from _pytest.runner import collect_one_node +from _pytest.runner import SetupState +from _pytest.warning_types import PytestWarning + + +if TYPE_CHECKING: + from typing_extensions import Self + + from _pytest.fixtures import FixtureManager + + +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "norecursedirs", + "Directory patterns to avoid for recursion", + type="args", + default=[ + "*.egg", + ".*", + "_darcs", + "build", + "CVS", + "dist", + "node_modules", + "venv", + "{arch}", + ], + ) + parser.addini( + "testpaths", + "Directories to search for tests when no files or directories are given on the " + "command line", + type="args", + default=[], + ) + group = parser.getgroup("general", "Running and selection options") + group._addoption( + "-x", + "--exitfirst", + action="store_const", + dest="maxfail", + const=1, + help="Exit instantly on first error or failed test", + ) + group = parser.getgroup("pytest-warnings") + group.addoption( + "-W", + "--pythonwarnings", + action="append", + help="Set which warnings to report, see -W option of Python itself", + ) + parser.addini( + "filterwarnings", + type="linelist", + help="Each line specifies a pattern for " + "warnings.filterwarnings. " + "Processed after -W/--pythonwarnings.", + ) + group._addoption( + "--maxfail", + metavar="num", + action="store", + type=int, + dest="maxfail", + default=0, + help="Exit after first num failures or errors", + ) + group._addoption( + "--strict-config", + action="store_true", + help="Any warnings encountered while parsing the `pytest` section of the " + "configuration file raise errors", + ) + group._addoption( + "--strict-markers", + action="store_true", + help="Markers not registered in the `markers` section of the configuration " + "file raise errors", + ) + group._addoption( + "--strict", + action="store_true", + help="(Deprecated) alias to --strict-markers", + ) + group._addoption( + "-c", + "--config-file", + metavar="FILE", + type=str, + dest="inifilename", + help="Load configuration from `FILE` instead of trying to locate one of the " + "implicit configuration files.", + ) + group._addoption( + "--continue-on-collection-errors", + action="store_true", + default=False, + dest="continue_on_collection_errors", + help="Force test execution even if collection errors occur", + ) + group._addoption( + "--rootdir", + action="store", + dest="rootdir", + help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', " + "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: " + "'$HOME/root_dir'.", + ) + + group = parser.getgroup("collect", "collection") + group.addoption( + "--collectonly", + "--collect-only", + "--co", + action="store_true", + help="Only collect tests, don't execute them", + ) + group.addoption( + "--pyargs", + action="store_true", + help="Try to interpret all arguments as Python packages", + ) + group.addoption( + "--ignore", + action="append", + metavar="path", + help="Ignore path during collection (multi-allowed)", + ) + group.addoption( + "--ignore-glob", + action="append", + metavar="path", + help="Ignore path pattern during collection (multi-allowed)", + ) + group.addoption( + "--deselect", + action="append", + metavar="nodeid_prefix", + help="Deselect item (via node id prefix) during collection (multi-allowed)", + ) + group.addoption( + "--confcutdir", + dest="confcutdir", + default=None, + metavar="dir", + type=functools.partial(directory_arg, optname="--confcutdir"), + help="Only load conftest.py's relative to specified dir", + ) + group.addoption( + "--noconftest", + action="store_true", + dest="noconftest", + default=False, + help="Don't load any conftest.py files", + ) + group.addoption( + "--keepduplicates", + "--keep-duplicates", + action="store_true", + dest="keepduplicates", + default=False, + help="Keep duplicate tests", + ) + group.addoption( + "--collect-in-virtualenv", + action="store_true", + dest="collect_in_virtualenv", + default=False, + help="Don't ignore tests in a local virtualenv directory", + ) + group.addoption( + "--import-mode", + default="prepend", + choices=["prepend", "append", "importlib"], + dest="importmode", + help="Prepend/append to sys.path when importing test modules and conftest " + "files. Default: prepend.", + ) + parser.addini( + "consider_namespace_packages", + type="bool", + default=False, + help="Consider namespace packages when resolving module names during import", + ) + + group = parser.getgroup("debugconfig", "test session debugging and configuration") + group.addoption( + "--basetemp", + dest="basetemp", + default=None, + type=validate_basetemp, + metavar="dir", + help=( + "Base temporary directory for this test run. " + "(Warning: this directory is removed if it exists.)" + ), + ) + + +def validate_basetemp(path: str) -> str: + # GH 7119 + msg = "basetemp must not be empty, the current working directory or any parent directory of it" + + # empty path + if not path: + raise argparse.ArgumentTypeError(msg) + + def is_ancestor(base: Path, query: Path) -> bool: + """Return whether query is an ancestor of base.""" + if base == query: + return True + return query in base.parents + + # check if path is an ancestor of cwd + if is_ancestor(Path.cwd(), Path(path).absolute()): + raise argparse.ArgumentTypeError(msg) + + # check symlinks for ancestors + if is_ancestor(Path.cwd().resolve(), Path(path).resolve()): + raise argparse.ArgumentTypeError(msg) + + return path + + +def wrap_session( + config: Config, doit: Callable[[Config, Session], int | ExitCode | None] +) -> int | ExitCode: + """Skeleton command line program.""" + session = Session.from_config(config) + session.exitstatus = ExitCode.OK + initstate = 0 + try: + try: + config._do_configure() + initstate = 1 + config.hook.pytest_sessionstart(session=session) + initstate = 2 + session.exitstatus = doit(config, session) or 0 + except UsageError: + session.exitstatus = ExitCode.USAGE_ERROR + raise + except Failed: + session.exitstatus = ExitCode.TESTS_FAILED + except (KeyboardInterrupt, exit.Exception): + excinfo = _pytest._code.ExceptionInfo.from_current() + exitstatus: int | ExitCode = ExitCode.INTERRUPTED + if isinstance(excinfo.value, exit.Exception): + if excinfo.value.returncode is not None: + exitstatus = excinfo.value.returncode + if initstate < 2: + sys.stderr.write(f"{excinfo.typename}: {excinfo.value.msg}\n") + config.hook.pytest_keyboard_interrupt(excinfo=excinfo) + session.exitstatus = exitstatus + except BaseException: + session.exitstatus = ExitCode.INTERNAL_ERROR + excinfo = _pytest._code.ExceptionInfo.from_current() + try: + config.notify_exception(excinfo, config.option) + except exit.Exception as exc: + if exc.returncode is not None: + session.exitstatus = exc.returncode + sys.stderr.write(f"{type(exc).__name__}: {exc}\n") + else: + if isinstance(excinfo.value, SystemExit): + sys.stderr.write("mainloop: caught unexpected SystemExit!\n") + + finally: + # Explicitly break reference cycle. + excinfo = None # type: ignore + os.chdir(session.startpath) + if initstate >= 2: + try: + config.hook.pytest_sessionfinish( + session=session, exitstatus=session.exitstatus + ) + except exit.Exception as exc: + if exc.returncode is not None: + session.exitstatus = exc.returncode + sys.stderr.write(f"{type(exc).__name__}: {exc}\n") + config._ensure_unconfigure() + return session.exitstatus + + +def pytest_cmdline_main(config: Config) -> int | ExitCode: + return wrap_session(config, _main) + + +def _main(config: Config, session: Session) -> int | ExitCode | None: + """Default command line protocol for initialization, session, + running tests and reporting.""" + config.hook.pytest_collection(session=session) + config.hook.pytest_runtestloop(session=session) + + if session.testsfailed: + return ExitCode.TESTS_FAILED + elif session.testscollected == 0: + return ExitCode.NO_TESTS_COLLECTED + return None + + +def pytest_collection(session: Session) -> None: + session.perform_collect() + + +def pytest_runtestloop(session: Session) -> bool: + if session.testsfailed and not session.config.option.continue_on_collection_errors: + raise session.Interrupted( + "%d error%s during collection" + % (session.testsfailed, "s" if session.testsfailed != 1 else "") + ) + + if session.config.option.collectonly: + return True + + for i, item in enumerate(session.items): + nextitem = session.items[i + 1] if i + 1 < len(session.items) else None + item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) + if session.shouldfail: + raise session.Failed(session.shouldfail) + if session.shouldstop: + raise session.Interrupted(session.shouldstop) + return True + + +def _in_venv(path: Path) -> bool: + """Attempt to detect if ``path`` is the root of a Virtual Environment by + checking for the existence of the pyvenv.cfg file. + + [https://peps.python.org/pep-0405/] + + For regression protection we also check for conda environments that do not include pyenv.cfg yet -- + https://github.com/conda/conda/issues/13337 is the conda issue tracking adding pyenv.cfg. + + Checking for the `conda-meta/history` file per https://github.com/pytest-dev/pytest/issues/12652#issuecomment-2246336902. + + """ + try: + return ( + path.joinpath("pyvenv.cfg").is_file() + or path.joinpath("conda-meta", "history").is_file() + ) + except OSError: + return False + + +def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: + if collection_path.name == "__pycache__": + return True + + ignore_paths = config._getconftest_pathlist( + "collect_ignore", path=collection_path.parent + ) + ignore_paths = ignore_paths or [] + excludeopt = config.getoption("ignore") + if excludeopt: + ignore_paths.extend(absolutepath(x) for x in excludeopt) + + if collection_path in ignore_paths: + return True + + ignore_globs = config._getconftest_pathlist( + "collect_ignore_glob", path=collection_path.parent + ) + ignore_globs = ignore_globs or [] + excludeglobopt = config.getoption("ignore_glob") + if excludeglobopt: + ignore_globs.extend(absolutepath(x) for x in excludeglobopt) + + if any(fnmatch.fnmatch(str(collection_path), str(glob)) for glob in ignore_globs): + return True + + allow_in_venv = config.getoption("collect_in_virtualenv") + if not allow_in_venv and _in_venv(collection_path): + return True + + if collection_path.is_dir(): + norecursepatterns = config.getini("norecursedirs") + if any(fnmatch_ex(pat, collection_path) for pat in norecursepatterns): + return True + + return None + + +def pytest_collect_directory( + path: Path, parent: nodes.Collector +) -> nodes.Collector | None: + return Dir.from_parent(parent, path=path) + + +def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None: + deselect_prefixes = tuple(config.getoption("deselect") or []) + if not deselect_prefixes: + return + + remaining = [] + deselected = [] + for colitem in items: + if colitem.nodeid.startswith(deselect_prefixes): + deselected.append(colitem) + else: + remaining.append(colitem) + + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = remaining + + +class FSHookProxy: + def __init__( + self, + pm: PytestPluginManager, + remove_mods: AbstractSet[object], + ) -> None: + self.pm = pm + self.remove_mods = remove_mods + + def __getattr__(self, name: str) -> pluggy.HookCaller: + x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods) + self.__dict__[name] = x + return x + + +class Interrupted(KeyboardInterrupt): + """Signals that the test run was interrupted.""" + + __module__ = "builtins" # For py3. + + +class Failed(Exception): + """Signals a stop as failed test run.""" + + +@dataclasses.dataclass +class _bestrelpath_cache(Dict[Path, str]): + __slots__ = ("path",) + + path: Path + + def __missing__(self, path: Path) -> str: + r = bestrelpath(self.path, path) + self[path] = r + return r + + +@final +class Dir(nodes.Directory): + """Collector of files in a file system directory. + + .. versionadded:: 8.0 + + .. note:: + + Python directories with an `__init__.py` file are instead collected by + :class:`~pytest.Package` by default. Both are :class:`~pytest.Directory` + collectors. + """ + + @classmethod + def from_parent( # type: ignore[override] + cls, + parent: nodes.Collector, + *, + path: Path, + ) -> Self: + """The public constructor. + + :param parent: The parent collector of this Dir. + :param path: The directory's path. + :type path: pathlib.Path + """ + return super().from_parent(parent=parent, path=path) + + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: + config = self.config + col: nodes.Collector | None + cols: Sequence[nodes.Collector] + ihook = self.ihook + for direntry in scandir(self.path): + if direntry.is_dir(): + path = Path(direntry.path) + if not self.session.isinitpath(path, with_parents=True): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + col = ihook.pytest_collect_directory(path=path, parent=self) + if col is not None: + yield col + + elif direntry.is_file(): + path = Path(direntry.path) + if not self.session.isinitpath(path): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + cols = ihook.pytest_collect_file(file_path=path, parent=self) + yield from cols + + +@final +class Session(nodes.Collector): + """The root of the collection tree. + + ``Session`` collects the initial paths given as arguments to pytest. + """ + + Interrupted = Interrupted + Failed = Failed + # Set on the session by runner.pytest_sessionstart. + _setupstate: SetupState + # Set on the session by fixtures.pytest_sessionstart. + _fixturemanager: FixtureManager + exitstatus: int | ExitCode + + def __init__(self, config: Config) -> None: + super().__init__( + name="", + path=config.rootpath, + fspath=None, + parent=None, + config=config, + session=self, + nodeid="", + ) + self.testsfailed = 0 + self.testscollected = 0 + self._shouldstop: bool | str = False + self._shouldfail: bool | str = False + self.trace = config.trace.root.get("collection") + self._initialpaths: frozenset[Path] = frozenset() + self._initialpaths_with_parents: frozenset[Path] = frozenset() + self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = [] + self._initial_parts: list[CollectionArgument] = [] + self._collection_cache: dict[nodes.Collector, CollectReport] = {} + self.items: list[nodes.Item] = [] + + self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) + + self.config.pluginmanager.register(self, name="session") + + @classmethod + def from_config(cls, config: Config) -> Session: + session: Session = cls._create(config=config) + return session + + def __repr__(self) -> str: + return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % ( + self.__class__.__name__, + self.name, + getattr(self, "exitstatus", ""), + self.testsfailed, + self.testscollected, + ) + + @property + def shouldstop(self) -> bool | str: + return self._shouldstop + + @shouldstop.setter + def shouldstop(self, value: bool | str) -> None: + # The runner checks shouldfail and assumes that if it is set we are + # definitely stopping, so prevent unsetting it. + if value is False and self._shouldstop: + warnings.warn( + PytestWarning( + "session.shouldstop cannot be unset after it has been set; ignoring." + ), + stacklevel=2, + ) + return + self._shouldstop = value + + @property + def shouldfail(self) -> bool | str: + return self._shouldfail + + @shouldfail.setter + def shouldfail(self, value: bool | str) -> None: + # The runner checks shouldfail and assumes that if it is set we are + # definitely stopping, so prevent unsetting it. + if value is False and self._shouldfail: + warnings.warn( + PytestWarning( + "session.shouldfail cannot be unset after it has been set; ignoring." + ), + stacklevel=2, + ) + return + self._shouldfail = value + + @property + def startpath(self) -> Path: + """The path from which pytest was invoked. + + .. versionadded:: 7.0.0 + """ + return self.config.invocation_params.dir + + def _node_location_to_relpath(self, node_path: Path) -> str: + # bestrelpath is a quite slow function. + return self._bestrelpathcache[node_path] + + @hookimpl(tryfirst=True) + def pytest_collectstart(self) -> None: + if self.shouldfail: + raise self.Failed(self.shouldfail) + if self.shouldstop: + raise self.Interrupted(self.shouldstop) + + @hookimpl(tryfirst=True) + def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: + if report.failed and not hasattr(report, "wasxfail"): + self.testsfailed += 1 + maxfail = self.config.getvalue("maxfail") + if maxfail and self.testsfailed >= maxfail: + self.shouldfail = "stopping after %d failures" % (self.testsfailed) + + pytest_collectreport = pytest_runtest_logreport + + def isinitpath( + self, + path: str | os.PathLike[str], + *, + with_parents: bool = False, + ) -> bool: + """Is path an initial path? + + An initial path is a path explicitly given to pytest on the command + line. + + :param with_parents: + If set, also return True if the path is a parent of an initial path. + + .. versionchanged:: 8.0 + Added the ``with_parents`` parameter. + """ + # Optimization: Path(Path(...)) is much slower than isinstance. + path_ = path if isinstance(path, Path) else Path(path) + if with_parents: + return path_ in self._initialpaths_with_parents + else: + return path_ in self._initialpaths + + def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: + # Optimization: Path(Path(...)) is much slower than isinstance. + path = fspath if isinstance(fspath, Path) else Path(fspath) + pm = self.config.pluginmanager + # Check if we have the common case of running + # hooks with all conftest.py files. + my_conftestmodules = pm._getconftestmodules(path) + remove_mods = pm._conftest_plugins.difference(my_conftestmodules) + proxy: pluggy.HookRelay + if remove_mods: + # One or more conftests are not in use at this path. + proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment] + else: + # All plugins are active for this fspath. + proxy = self.config.hook + return proxy + + def _collect_path( + self, + path: Path, + path_cache: dict[Path, Sequence[nodes.Collector]], + ) -> Sequence[nodes.Collector]: + """Create a Collector for the given path. + + `path_cache` makes it so the same Collectors are returned for the same + path. + """ + if path in path_cache: + return path_cache[path] + + if path.is_dir(): + ihook = self.gethookproxy(path.parent) + col: nodes.Collector | None = ihook.pytest_collect_directory( + path=path, parent=self + ) + cols: Sequence[nodes.Collector] = (col,) if col is not None else () + + elif path.is_file(): + ihook = self.gethookproxy(path) + cols = ihook.pytest_collect_file(file_path=path, parent=self) + + else: + # Broken symlink or invalid/missing file. + cols = () + + path_cache[path] = cols + return cols + + @overload + def perform_collect( + self, args: Sequence[str] | None = ..., genitems: Literal[True] = ... + ) -> Sequence[nodes.Item]: ... + + @overload + def perform_collect( + self, args: Sequence[str] | None = ..., genitems: bool = ... + ) -> Sequence[nodes.Item | nodes.Collector]: ... + + def perform_collect( + self, args: Sequence[str] | None = None, genitems: bool = True + ) -> Sequence[nodes.Item | nodes.Collector]: + """Perform the collection phase for this session. + + This is called by the default :hook:`pytest_collection` hook + implementation; see the documentation of this hook for more details. + For testing purposes, it may also be called directly on a fresh + ``Session``. + + This function normally recursively expands any collectors collected + from the session to their items, and only items are returned. For + testing purposes, this may be suppressed by passing ``genitems=False``, + in which case the return value contains these collectors unexpanded, + and ``session.items`` is empty. + """ + if args is None: + args = self.config.args + + self.trace("perform_collect", self, args) + self.trace.root.indent += 1 + + hook = self.config.hook + + self._notfound = [] + self._initial_parts = [] + self._collection_cache = {} + self.items = [] + items: Sequence[nodes.Item | nodes.Collector] = self.items + try: + initialpaths: list[Path] = [] + initialpaths_with_parents: list[Path] = [] + for arg in args: + collection_argument = resolve_collection_argument( + self.config.invocation_params.dir, + arg, + as_pypath=self.config.option.pyargs, + ) + self._initial_parts.append(collection_argument) + initialpaths.append(collection_argument.path) + initialpaths_with_parents.append(collection_argument.path) + initialpaths_with_parents.extend(collection_argument.path.parents) + self._initialpaths = frozenset(initialpaths) + self._initialpaths_with_parents = frozenset(initialpaths_with_parents) + + rep = collect_one_node(self) + self.ihook.pytest_collectreport(report=rep) + self.trace.root.indent -= 1 + if self._notfound: + errors = [] + for arg, collectors in self._notfound: + if collectors: + errors.append( + f"not found: {arg}\n(no match in any of {collectors!r})" + ) + else: + errors.append(f"found no collectors for {arg}") + + raise UsageError(*errors) + + if not genitems: + items = rep.result + else: + if rep.passed: + for node in rep.result: + self.items.extend(self.genitems(node)) + + self.config.pluginmanager.check_pending() + hook.pytest_collection_modifyitems( + session=self, config=self.config, items=items + ) + finally: + self._notfound = [] + self._initial_parts = [] + self._collection_cache = {} + hook.pytest_collection_finish(session=self) + + if genitems: + self.testscollected = len(items) + + return items + + def _collect_one_node( + self, + node: nodes.Collector, + handle_dupes: bool = True, + ) -> tuple[CollectReport, bool]: + if node in self._collection_cache and handle_dupes: + rep = self._collection_cache[node] + return rep, True + else: + rep = collect_one_node(node) + self._collection_cache[node] = rep + return rep, False + + def collect(self) -> Iterator[nodes.Item | nodes.Collector]: + # This is a cache for the root directories of the initial paths. + # We can't use collection_cache for Session because of its special + # role as the bootstrapping collector. + path_cache: dict[Path, Sequence[nodes.Collector]] = {} + + pm = self.config.pluginmanager + + for collection_argument in self._initial_parts: + self.trace("processing argument", collection_argument) + self.trace.root.indent += 1 + + argpath = collection_argument.path + names = collection_argument.parts + module_name = collection_argument.module_name + + # resolve_collection_argument() ensures this. + if argpath.is_dir(): + assert not names, f"invalid arg {(argpath, names)!r}" + + paths = [argpath] + # Add relevant parents of the path, from the root, e.g. + # /a/b/c.py -> [/, /a, /a/b, /a/b/c.py] + if module_name is None: + # Paths outside of the confcutdir should not be considered. + for path in argpath.parents: + if not pm._is_in_confcutdir(path): + break + paths.insert(0, path) + else: + # For --pyargs arguments, only consider paths matching the module + # name. Paths beyond the package hierarchy are not included. + module_name_parts = module_name.split(".") + for i, path in enumerate(argpath.parents, 2): + if i > len(module_name_parts) or path.stem != module_name_parts[-i]: + break + paths.insert(0, path) + + # Start going over the parts from the root, collecting each level + # and discarding all nodes which don't match the level's part. + any_matched_in_initial_part = False + notfound_collectors = [] + work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [ + (self, [*paths, *names]) + ] + while work: + matchnode, matchparts = work.pop() + + # Pop'd all of the parts, this is a match. + if not matchparts: + yield matchnode + any_matched_in_initial_part = True + continue + + # Should have been matched by now, discard. + if not isinstance(matchnode, nodes.Collector): + continue + + # Collect this level of matching. + # Collecting Session (self) is done directly to avoid endless + # recursion to this function. + subnodes: Sequence[nodes.Collector | nodes.Item] + if isinstance(matchnode, Session): + assert isinstance(matchparts[0], Path) + subnodes = matchnode._collect_path(matchparts[0], path_cache) + else: + # For backward compat, files given directly multiple + # times on the command line should not be deduplicated. + handle_dupes = not ( + len(matchparts) == 1 + and isinstance(matchparts[0], Path) + and matchparts[0].is_file() + ) + rep, duplicate = self._collect_one_node(matchnode, handle_dupes) + if not duplicate and not rep.passed: + # Report collection failures here to avoid failing to + # run some test specified in the command line because + # the module could not be imported (#134). + matchnode.ihook.pytest_collectreport(report=rep) + if not rep.passed: + continue + subnodes = rep.result + + # Prune this level. + any_matched_in_collector = False + for node in reversed(subnodes): + # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`. + if isinstance(matchparts[0], Path): + is_match = node.path == matchparts[0] + if sys.platform == "win32" and not is_match: + # In case the file paths do not match, fallback to samefile() to + # account for short-paths on Windows (#11895). + same_file = os.path.samefile(node.path, matchparts[0]) + # We don't want to match links to the current node, + # otherwise we would match the same file more than once (#12039). + is_match = same_file and ( + os.path.islink(node.path) + == os.path.islink(matchparts[0]) + ) + + # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. + else: + # TODO: Remove parametrized workaround once collection structure contains + # parametrization. + is_match = ( + node.name == matchparts[0] + or node.name.split("[")[0] == matchparts[0] + ) + if is_match: + work.append((node, matchparts[1:])) + any_matched_in_collector = True + + if not any_matched_in_collector: + notfound_collectors.append(matchnode) + + if not any_matched_in_initial_part: + report_arg = "::".join((str(argpath), *names)) + self._notfound.append((report_arg, notfound_collectors)) + + self.trace.root.indent -= 1 + + def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]: + self.trace("genitems", node) + if isinstance(node, nodes.Item): + node.ihook.pytest_itemcollected(item=node) + yield node + else: + assert isinstance(node, nodes.Collector) + keepduplicates = self.config.getoption("keepduplicates") + # For backward compat, dedup only applies to files. + handle_dupes = not (keepduplicates and isinstance(node, nodes.File)) + rep, duplicate = self._collect_one_node(node, handle_dupes) + if duplicate and not keepduplicates: + return + if rep.passed: + for subnode in rep.result: + yield from self.genitems(subnode) + if not duplicate: + node.ihook.pytest_collectreport(report=rep) + + +def search_pypath(module_name: str) -> str | None: + """Search sys.path for the given a dotted module name, and return its file + system path if found.""" + try: + spec = importlib.util.find_spec(module_name) + # AttributeError: looks like package module, but actually filename + # ImportError: module does not exist + # ValueError: not a module name + except (AttributeError, ImportError, ValueError): + return None + if spec is None or spec.origin is None or spec.origin == "namespace": + return None + elif spec.submodule_search_locations: + return os.path.dirname(spec.origin) + else: + return spec.origin + + +@dataclasses.dataclass(frozen=True) +class CollectionArgument: + """A resolved collection argument.""" + + path: Path + parts: Sequence[str] + module_name: str | None + + +def resolve_collection_argument( + invocation_path: Path, arg: str, *, as_pypath: bool = False +) -> CollectionArgument: + """Parse path arguments optionally containing selection parts and return (fspath, names). + + Command-line arguments can point to files and/or directories, and optionally contain + parts for specific tests selection, for example: + + "pkg/tests/test_foo.py::TestClass::test_foo" + + This function ensures the path exists, and returns a resolved `CollectionArgument`: + + CollectionArgument( + path=Path("/full/path/to/pkg/tests/test_foo.py"), + parts=["TestClass", "test_foo"], + module_name=None, + ) + + When as_pypath is True, expects that the command-line argument actually contains + module paths instead of file-system paths: + + "pkg.tests.test_foo::TestClass::test_foo" + + In which case we search sys.path for a matching module, and then return the *path* to the + found module, which may look like this: + + CollectionArgument( + path=Path("/home/u/myvenv/lib/site-packages/pkg/tests/test_foo.py"), + parts=["TestClass", "test_foo"], + module_name="pkg.tests.test_foo", + ) + + If the path doesn't exist, raise UsageError. + If the path is a directory and selection parts are present, raise UsageError. + """ + base, squacket, rest = str(arg).partition("[") + strpath, *parts = base.split("::") + if parts: + parts[-1] = f"{parts[-1]}{squacket}{rest}" + module_name = None + if as_pypath: + pyarg_strpath = search_pypath(strpath) + if pyarg_strpath is not None: + module_name = strpath + strpath = pyarg_strpath + fspath = invocation_path / strpath + fspath = absolutepath(fspath) + if not safe_exists(fspath): + msg = ( + "module or package not found: {arg} (missing __init__.py?)" + if as_pypath + else "file or directory not found: {arg}" + ) + raise UsageError(msg.format(arg=arg)) + if parts and fspath.is_dir(): + msg = ( + "package argument cannot contain :: selection parts: {arg}" + if as_pypath + else "directory argument cannot contain :: selection parts: {arg}" + ) + raise UsageError(msg.format(arg=arg)) + return CollectionArgument( + path=fspath, + parts=parts, + module_name=module_name, + ) diff --git a/.venv/Lib/site-packages/_pytest/mark/__init__.py b/.venv/Lib/site-packages/_pytest/mark/__init__.py new file mode 100644 index 0000000..a4f942c --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/mark/__init__.py @@ -0,0 +1,292 @@ +"""Generic mechanism for marking and selecting python functions.""" + +from __future__ import annotations + +import collections +import dataclasses +from typing import AbstractSet +from typing import Collection +from typing import Iterable +from typing import Optional +from typing import TYPE_CHECKING + +from .expression import Expression +from .expression import ParseError +from .structures import EMPTY_PARAMETERSET_OPTION +from .structures import get_empty_parameterset_mark +from .structures import Mark +from .structures import MARK_GEN +from .structures import MarkDecorator +from .structures import MarkGenerator +from .structures import ParameterSet +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config import UsageError +from _pytest.config.argparsing import NOT_SET +from _pytest.config.argparsing import Parser +from _pytest.stash import StashKey + + +if TYPE_CHECKING: + from _pytest.nodes import Item + + +__all__ = [ + "MARK_GEN", + "Mark", + "MarkDecorator", + "MarkGenerator", + "ParameterSet", + "get_empty_parameterset_mark", +] + + +old_mark_config_key = StashKey[Optional[Config]]() + + +def param( + *values: object, + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | None = None, +) -> ParameterSet: + """Specify a parameter in `pytest.mark.parametrize`_ calls or + :ref:`parametrized fixtures `. + + .. code-block:: python + + @pytest.mark.parametrize( + "test_input,expected", + [ + ("3+5", 8), + pytest.param("6*9", 42, marks=pytest.mark.xfail), + ], + ) + def test_eval(test_input, expected): + assert eval(test_input) == expected + + :param values: Variable args of the values of the parameter set, in order. + :param marks: A single mark or a list of marks to be applied to this parameter set. + :param id: The id to attribute to this parameter set. + """ + return ParameterSet.param(*values, marks=marks, id=id) + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("general") + group._addoption( + "-k", + action="store", + dest="keyword", + default="", + metavar="EXPRESSION", + help="Only run tests which match the given substring expression. " + "An expression is a Python evaluable expression " + "where all names are substring-matched against test names " + "and their parent classes. Example: -k 'test_method or test_" + "other' matches all test functions and classes whose name " + "contains 'test_method' or 'test_other', while -k 'not test_method' " + "matches those that don't contain 'test_method' in their names. " + "-k 'not test_method and not test_other' will eliminate the matches. " + "Additionally keywords are matched to classes and functions " + "containing extra names in their 'extra_keyword_matches' set, " + "as well as functions which have names assigned directly to them. " + "The matching is case-insensitive.", + ) + + group._addoption( + "-m", + action="store", + dest="markexpr", + default="", + metavar="MARKEXPR", + help="Only run tests matching given mark expression. " + "For example: -m 'mark1 and not mark2'.", + ) + + group.addoption( + "--markers", + action="store_true", + help="show markers (builtin, plugin and per-project ones).", + ) + + parser.addini("markers", "Register new markers for test functions", "linelist") + parser.addini(EMPTY_PARAMETERSET_OPTION, "Default marker for empty parametersets") + + +@hookimpl(tryfirst=True) +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: + import _pytest.config + + if config.option.markers: + config._do_configure() + tw = _pytest.config.create_terminal_writer(config) + for line in config.getini("markers"): + parts = line.split(":", 1) + name = parts[0] + rest = parts[1] if len(parts) == 2 else "" + tw.write(f"@pytest.mark.{name}:", bold=True) + tw.line(rest) + tw.line() + config._ensure_unconfigure() + return 0 + + return None + + +@dataclasses.dataclass +class KeywordMatcher: + """A matcher for keywords. + + Given a list of names, matches any substring of one of these names. The + string inclusion check is case-insensitive. + + Will match on the name of colitem, including the names of its parents. + Only matches names of items which are either a :class:`Class` or a + :class:`Function`. + + Additionally, matches on names in the 'extra_keyword_matches' set of + any item, as well as names directly assigned to test functions. + """ + + __slots__ = ("_names",) + + _names: AbstractSet[str] + + @classmethod + def from_item(cls, item: Item) -> KeywordMatcher: + mapped_names = set() + + # Add the names of the current item and any parent items, + # except the Session and root Directory's which are not + # interesting for matching. + import pytest + + for node in item.listchain(): + if isinstance(node, pytest.Session): + continue + if isinstance(node, pytest.Directory) and isinstance( + node.parent, pytest.Session + ): + continue + mapped_names.add(node.name) + + # Add the names added as extra keywords to current or parent items. + mapped_names.update(item.listextrakeywords()) + + # Add the names attached to the current function through direct assignment. + function_obj = getattr(item, "function", None) + if function_obj: + mapped_names.update(function_obj.__dict__) + + # Add the markers to the keywords as we no longer handle them correctly. + mapped_names.update(mark.name for mark in item.iter_markers()) + + return cls(mapped_names) + + def __call__(self, subname: str, /, **kwargs: str | int | bool | None) -> bool: + if kwargs: + raise UsageError("Keyword expressions do not support call parameters.") + subname = subname.lower() + names = (name.lower() for name in self._names) + + for name in names: + if subname in name: + return True + return False + + +def deselect_by_keyword(items: list[Item], config: Config) -> None: + keywordexpr = config.option.keyword.lstrip() + if not keywordexpr: + return + + expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'") + + remaining = [] + deselected = [] + for colitem in items: + if not expr.evaluate(KeywordMatcher.from_item(colitem)): + deselected.append(colitem) + else: + remaining.append(colitem) + + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = remaining + + +@dataclasses.dataclass +class MarkMatcher: + """A matcher for markers which are present. + + Tries to match on any marker names, attached to the given colitem. + """ + + __slots__ = ("own_mark_name_mapping",) + + own_mark_name_mapping: dict[str, list[Mark]] + + @classmethod + def from_markers(cls, markers: Iterable[Mark]) -> MarkMatcher: + mark_name_mapping = collections.defaultdict(list) + for mark in markers: + mark_name_mapping[mark.name].append(mark) + return cls(mark_name_mapping) + + def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: + if not (matches := self.own_mark_name_mapping.get(name, [])): + return False + + for mark in matches: + if all(mark.kwargs.get(k, NOT_SET) == v for k, v in kwargs.items()): + return True + + return False + + +def deselect_by_mark(items: list[Item], config: Config) -> None: + matchexpr = config.option.markexpr + if not matchexpr: + return + + expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'") + remaining: list[Item] = [] + deselected: list[Item] = [] + for item in items: + if expr.evaluate(MarkMatcher.from_markers(item.iter_markers())): + remaining.append(item) + else: + deselected.append(item) + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = remaining + + +def _parse_expression(expr: str, exc_message: str) -> Expression: + try: + return Expression.compile(expr) + except ParseError as e: + raise UsageError(f"{exc_message}: {expr}: {e}") from None + + +def pytest_collection_modifyitems(items: list[Item], config: Config) -> None: + deselect_by_keyword(items, config) + deselect_by_mark(items, config) + + +def pytest_configure(config: Config) -> None: + config.stash[old_mark_config_key] = MARK_GEN._config + MARK_GEN._config = config + + empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) + + if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""): + raise UsageError( + f"{EMPTY_PARAMETERSET_OPTION!s} must be one of skip, xfail or fail_at_collect" + f" but it is {empty_parameterset!r}" + ) + + +def pytest_unconfigure(config: Config) -> None: + MARK_GEN._config = config.stash.get(old_mark_config_key, None) diff --git a/.venv/Lib/site-packages/_pytest/mark/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/mark/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..201662db63c1ecad9e47b15ea8a93aa9a432d76b GIT binary patch literal 12517 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 IMEBpn0-yj;egFUf literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/mark/__pycache__/structures.cpython-312.pyc b/.venv/Lib/site-packages/_pytest/mark/__pycache__/structures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba0430d88782654f90c463d0211086f7d13a548f GIT binary patch literal 25716 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/_pytest/mark/expression.py b/.venv/Lib/site-packages/_pytest/mark/expression.py new file mode 100644 index 0000000..89cc0e9 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/mark/expression.py @@ -0,0 +1,333 @@ +r"""Evaluate match expressions, as used by `-k` and `-m`. + +The grammar is: + +expression: expr? EOF +expr: and_expr ('or' and_expr)* +and_expr: not_expr ('and' not_expr)* +not_expr: 'not' not_expr | '(' expr ')' | ident kwargs? + +ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ +kwargs: ('(' name '=' value ( ', ' name '=' value )* ')') +name: a valid ident, but not a reserved keyword +value: (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None' + +The semantics are: + +- Empty expression evaluates to False. +- ident evaluates to True or False according to a provided matcher function. +- or/and/not evaluate according to the usual boolean semantics. +- ident with parentheses and keyword arguments evaluates to True or False according to a provided matcher function. +""" + +from __future__ import annotations + +import ast +import dataclasses +import enum +import keyword +import re +import types +from typing import Iterator +from typing import Literal +from typing import Mapping +from typing import NoReturn +from typing import overload +from typing import Protocol +from typing import Sequence + + +__all__ = [ + "Expression", + "ParseError", +] + + +class TokenType(enum.Enum): + LPAREN = "left parenthesis" + RPAREN = "right parenthesis" + OR = "or" + AND = "and" + NOT = "not" + IDENT = "identifier" + EOF = "end of input" + EQUAL = "=" + STRING = "string literal" + COMMA = "," + + +@dataclasses.dataclass(frozen=True) +class Token: + __slots__ = ("type", "value", "pos") + type: TokenType + value: str + pos: int + + +class ParseError(Exception): + """The expression contains invalid syntax. + + :param column: The column in the line where the error occurred (1-based). + :param message: A description of the error. + """ + + def __init__(self, column: int, message: str) -> None: + self.column = column + self.message = message + + def __str__(self) -> str: + return f"at column {self.column}: {self.message}" + + +class Scanner: + __slots__ = ("tokens", "current") + + def __init__(self, input: str) -> None: + self.tokens = self.lex(input) + self.current = next(self.tokens) + + def lex(self, input: str) -> Iterator[Token]: + pos = 0 + while pos < len(input): + if input[pos] in (" ", "\t"): + pos += 1 + elif input[pos] == "(": + yield Token(TokenType.LPAREN, "(", pos) + pos += 1 + elif input[pos] == ")": + yield Token(TokenType.RPAREN, ")", pos) + pos += 1 + elif input[pos] == "=": + yield Token(TokenType.EQUAL, "=", pos) + pos += 1 + elif input[pos] == ",": + yield Token(TokenType.COMMA, ",", pos) + pos += 1 + elif (quote_char := input[pos]) in ("'", '"'): + end_quote_pos = input.find(quote_char, pos + 1) + if end_quote_pos == -1: + raise ParseError( + pos + 1, + f'closing quote "{quote_char}" is missing', + ) + value = input[pos : end_quote_pos + 1] + if (backslash_pos := input.find("\\")) != -1: + raise ParseError( + backslash_pos + 1, + r'escaping with "\" not supported in marker expression', + ) + yield Token(TokenType.STRING, value, pos) + pos += len(value) + else: + match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\|/)+", input[pos:]) + if match: + value = match.group(0) + if value == "or": + yield Token(TokenType.OR, value, pos) + elif value == "and": + yield Token(TokenType.AND, value, pos) + elif value == "not": + yield Token(TokenType.NOT, value, pos) + else: + yield Token(TokenType.IDENT, value, pos) + pos += len(value) + else: + raise ParseError( + pos + 1, + f'unexpected character "{input[pos]}"', + ) + yield Token(TokenType.EOF, "", pos) + + @overload + def accept(self, type: TokenType, *, reject: Literal[True]) -> Token: ... + + @overload + def accept( + self, type: TokenType, *, reject: Literal[False] = False + ) -> Token | None: ... + + def accept(self, type: TokenType, *, reject: bool = False) -> Token | None: + if self.current.type is type: + token = self.current + if token.type is not TokenType.EOF: + self.current = next(self.tokens) + return token + if reject: + self.reject((type,)) + return None + + def reject(self, expected: Sequence[TokenType]) -> NoReturn: + raise ParseError( + self.current.pos + 1, + "expected {}; got {}".format( + " OR ".join(type.value for type in expected), + self.current.type.value, + ), + ) + + +# True, False and None are legal match expression identifiers, +# but illegal as Python identifiers. To fix this, this prefix +# is added to identifiers in the conversion to Python AST. +IDENT_PREFIX = "$" + + +def expression(s: Scanner) -> ast.Expression: + if s.accept(TokenType.EOF): + ret: ast.expr = ast.Constant(False) + else: + ret = expr(s) + s.accept(TokenType.EOF, reject=True) + return ast.fix_missing_locations(ast.Expression(ret)) + + +def expr(s: Scanner) -> ast.expr: + ret = and_expr(s) + while s.accept(TokenType.OR): + rhs = and_expr(s) + ret = ast.BoolOp(ast.Or(), [ret, rhs]) + return ret + + +def and_expr(s: Scanner) -> ast.expr: + ret = not_expr(s) + while s.accept(TokenType.AND): + rhs = not_expr(s) + ret = ast.BoolOp(ast.And(), [ret, rhs]) + return ret + + +def not_expr(s: Scanner) -> ast.expr: + if s.accept(TokenType.NOT): + return ast.UnaryOp(ast.Not(), not_expr(s)) + if s.accept(TokenType.LPAREN): + ret = expr(s) + s.accept(TokenType.RPAREN, reject=True) + return ret + ident = s.accept(TokenType.IDENT) + if ident: + name = ast.Name(IDENT_PREFIX + ident.value, ast.Load()) + if s.accept(TokenType.LPAREN): + ret = ast.Call(func=name, args=[], keywords=all_kwargs(s)) + s.accept(TokenType.RPAREN, reject=True) + else: + ret = name + return ret + + s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT)) + + +BUILTIN_MATCHERS = {"True": True, "False": False, "None": None} + + +def single_kwarg(s: Scanner) -> ast.keyword: + keyword_name = s.accept(TokenType.IDENT, reject=True) + if not keyword_name.value.isidentifier(): + raise ParseError( + keyword_name.pos + 1, + f"not a valid python identifier {keyword_name.value}", + ) + if keyword.iskeyword(keyword_name.value): + raise ParseError( + keyword_name.pos + 1, + f"unexpected reserved python keyword `{keyword_name.value}`", + ) + s.accept(TokenType.EQUAL, reject=True) + + if value_token := s.accept(TokenType.STRING): + value: str | int | bool | None = value_token.value[1:-1] # strip quotes + else: + value_token = s.accept(TokenType.IDENT, reject=True) + if ( + (number := value_token.value).isdigit() + or number.startswith("-") + and number[1:].isdigit() + ): + value = int(number) + elif value_token.value in BUILTIN_MATCHERS: + value = BUILTIN_MATCHERS[value_token.value] + else: + raise ParseError( + value_token.pos + 1, + f'unexpected character/s "{value_token.value}"', + ) + + ret = ast.keyword(keyword_name.value, ast.Constant(value)) + return ret + + +def all_kwargs(s: Scanner) -> list[ast.keyword]: + ret = [single_kwarg(s)] + while s.accept(TokenType.COMMA): + ret.append(single_kwarg(s)) + return ret + + +class MatcherCall(Protocol): + def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: ... + + +@dataclasses.dataclass +class MatcherNameAdapter: + matcher: MatcherCall + name: str + + def __bool__(self) -> bool: + return self.matcher(self.name) + + def __call__(self, **kwargs: str | int | bool | None) -> bool: + return self.matcher(self.name, **kwargs) + + +class MatcherAdapter(Mapping[str, MatcherNameAdapter]): + """Adapts a matcher function to a locals mapping as required by eval().""" + + def __init__(self, matcher: MatcherCall) -> None: + self.matcher = matcher + + def __getitem__(self, key: str) -> MatcherNameAdapter: + return MatcherNameAdapter(matcher=self.matcher, name=key[len(IDENT_PREFIX) :]) + + def __iter__(self) -> Iterator[str]: + raise NotImplementedError() + + def __len__(self) -> int: + raise NotImplementedError() + + +class Expression: + """A compiled match expression as used by -k and -m. + + The expression can be evaluated against different matchers. + """ + + __slots__ = ("code",) + + def __init__(self, code: types.CodeType) -> None: + self.code = code + + @classmethod + def compile(self, input: str) -> Expression: + """Compile a match expression. + + :param input: The input expression - one line. + """ + astexpr = expression(Scanner(input)) + code: types.CodeType = compile( + astexpr, + filename="", + mode="eval", + ) + return Expression(code) + + def evaluate(self, matcher: MatcherCall) -> bool: + """Evaluate the match expression. + + :param matcher: + Given an identifier, should return whether it matches or not. + Should be prepared to handle arbitrary strings as input. + + :returns: Whether the expression matches or not. + """ + ret: bool = bool(eval(self.code, {"__builtins__": {}}, MatcherAdapter(matcher))) + return ret diff --git a/.venv/Lib/site-packages/_pytest/mark/structures.py b/.venv/Lib/site-packages/_pytest/mark/structures.py new file mode 100644 index 0000000..92ade55 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/mark/structures.py @@ -0,0 +1,615 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import collections.abc +import dataclasses +import inspect +from typing import Any +from typing import Callable +from typing import Collection +from typing import final +from typing import Iterable +from typing import Iterator +from typing import Mapping +from typing import MutableMapping +from typing import NamedTuple +from typing import overload +from typing import Sequence +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union +import warnings + +from .._code import getfslineno +from ..compat import ascii_escaped +from ..compat import NOTSET +from ..compat import NotSetType +from _pytest.config import Config +from _pytest.deprecated import check_ispytest +from _pytest.deprecated import MARKED_FIXTURE +from _pytest.outcomes import fail +from _pytest.scope import _ScopeName +from _pytest.warning_types import PytestUnknownMarkWarning + + +if TYPE_CHECKING: + from ..nodes import Node + + +EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" + + +def istestfunc(func) -> bool: + return callable(func) and getattr(func, "__name__", "") != "" + + +def get_empty_parameterset_mark( + config: Config, argnames: Sequence[str], func +) -> MarkDecorator: + from ..nodes import Collector + + fs, lineno = getfslineno(func) + reason = "got empty parameter set %r, function %s at %s:%d" % ( + argnames, + func.__name__, + fs, + lineno, + ) + + requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION) + if requested_mark in ("", None, "skip"): + mark = MARK_GEN.skip(reason=reason) + elif requested_mark == "xfail": + mark = MARK_GEN.xfail(reason=reason, run=False) + elif requested_mark == "fail_at_collect": + f_name = func.__name__ + _, lineno = getfslineno(func) + raise Collector.CollectError( + "Empty parameter set in '%s' at line %d" % (f_name, lineno + 1) + ) + else: + raise LookupError(requested_mark) + return mark + + +class ParameterSet(NamedTuple): + values: Sequence[object | NotSetType] + marks: Collection[MarkDecorator | Mark] + id: str | None + + @classmethod + def param( + cls, + *values: object, + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | None = None, + ) -> ParameterSet: + if isinstance(marks, MarkDecorator): + marks = (marks,) + else: + assert isinstance(marks, collections.abc.Collection) + + if id is not None: + if not isinstance(id, str): + raise TypeError(f"Expected id to be a string, got {type(id)}: {id!r}") + id = ascii_escaped(id) + return cls(values, marks, id) + + @classmethod + def extract_from( + cls, + parameterset: ParameterSet | Sequence[object] | object, + force_tuple: bool = False, + ) -> ParameterSet: + """Extract from an object or objects. + + :param parameterset: + A legacy style parameterset that may or may not be a tuple, + and may or may not be wrapped into a mess of mark objects. + + :param force_tuple: + Enforce tuple wrapping so single argument tuple values + don't get decomposed and break tests. + """ + if isinstance(parameterset, cls): + return parameterset + if force_tuple: + return cls.param(parameterset) + else: + # TODO: Refactor to fix this type-ignore. Currently the following + # passes type-checking but crashes: + # + # @pytest.mark.parametrize(('x', 'y'), [1, 2]) + # def test_foo(x, y): pass + return cls(parameterset, marks=[], id=None) # type: ignore[arg-type] + + @staticmethod + def _parse_parametrize_args( + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + *args, + **kwargs, + ) -> tuple[Sequence[str], bool]: + if isinstance(argnames, str): + argnames = [x.strip() for x in argnames.split(",") if x.strip()] + force_tuple = len(argnames) == 1 + else: + force_tuple = False + return argnames, force_tuple + + @staticmethod + def _parse_parametrize_parameters( + argvalues: Iterable[ParameterSet | Sequence[object] | object], + force_tuple: bool, + ) -> list[ParameterSet]: + return [ + ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues + ] + + @classmethod + def _for_parametrize( + cls, + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + func, + config: Config, + nodeid: str, + ) -> tuple[Sequence[str], list[ParameterSet]]: + argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) + parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) + del argvalues + + if parameters: + # Check all parameter sets have the correct number of values. + for param in parameters: + if len(param.values) != len(argnames): + msg = ( + '{nodeid}: in "parametrize" the number of names ({names_len}):\n' + " {names}\n" + "must be equal to the number of values ({values_len}):\n" + " {values}" + ) + fail( + msg.format( + nodeid=nodeid, + values=param.values, + names=argnames, + names_len=len(argnames), + values_len=len(param.values), + ), + pytrace=False, + ) + else: + # Empty parameter set (likely computed at runtime): create a single + # parameter set with NOTSET values, with the "empty parameter set" mark applied to it. + mark = get_empty_parameterset_mark(config, argnames, func) + parameters.append( + ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) + ) + return argnames, parameters + + +@final +@dataclasses.dataclass(frozen=True) +class Mark: + """A pytest mark.""" + + #: Name of the mark. + name: str + #: Positional arguments of the mark decorator. + args: tuple[Any, ...] + #: Keyword arguments of the mark decorator. + kwargs: Mapping[str, Any] + + #: Source Mark for ids with parametrize Marks. + _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) + #: Resolved/generated ids with parametrize Marks. + _param_ids_generated: Sequence[str] | None = dataclasses.field( + default=None, repr=False + ) + + def __init__( + self, + name: str, + args: tuple[Any, ...], + kwargs: Mapping[str, Any], + param_ids_from: Mark | None = None, + param_ids_generated: Sequence[str] | None = None, + *, + _ispytest: bool = False, + ) -> None: + """:meta private:""" + check_ispytest(_ispytest) + # Weirdness to bypass frozen=True. + object.__setattr__(self, "name", name) + object.__setattr__(self, "args", args) + object.__setattr__(self, "kwargs", kwargs) + object.__setattr__(self, "_param_ids_from", param_ids_from) + object.__setattr__(self, "_param_ids_generated", param_ids_generated) + + def _has_param_ids(self) -> bool: + return "ids" in self.kwargs or len(self.args) >= 4 + + def combined_with(self, other: Mark) -> Mark: + """Return a new Mark which is a combination of this + Mark and another Mark. + + Combines by appending args and merging kwargs. + + :param Mark other: The mark to combine with. + :rtype: Mark + """ + assert self.name == other.name + + # Remember source of ids with parametrize Marks. + param_ids_from: Mark | None = None + if self.name == "parametrize": + if other._has_param_ids(): + param_ids_from = other + elif self._has_param_ids(): + param_ids_from = self + + return Mark( + self.name, + self.args + other.args, + dict(self.kwargs, **other.kwargs), + param_ids_from=param_ids_from, + _ispytest=True, + ) + + +# A generic parameter designating an object to which a Mark may +# be applied -- a test function (callable) or class. +# Note: a lambda is not allowed, but this can't be represented. +Markable = TypeVar("Markable", bound=Union[Callable[..., object], type]) + + +@dataclasses.dataclass +class MarkDecorator: + """A decorator for applying a mark on test functions and classes. + + ``MarkDecorators`` are created with ``pytest.mark``:: + + mark1 = pytest.mark.NAME # Simple MarkDecorator + mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator + + and can then be applied as decorators to test functions:: + + @mark2 + def test_function(): + pass + + When a ``MarkDecorator`` is called, it does the following: + + 1. If called with a single class as its only positional argument and no + additional keyword arguments, it attaches the mark to the class so it + gets applied automatically to all test cases found in that class. + + 2. If called with a single function as its only positional argument and + no additional keyword arguments, it attaches the mark to the function, + containing all the arguments already stored internally in the + ``MarkDecorator``. + + 3. When called in any other case, it returns a new ``MarkDecorator`` + instance with the original ``MarkDecorator``'s content updated with + the arguments passed to this call. + + Note: The rules above prevent a ``MarkDecorator`` from storing only a + single function or class reference as its positional argument with no + additional keyword or positional arguments. You can work around this by + using `with_args()`. + """ + + mark: Mark + + def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None: + """:meta private:""" + check_ispytest(_ispytest) + self.mark = mark + + @property + def name(self) -> str: + """Alias for mark.name.""" + return self.mark.name + + @property + def args(self) -> tuple[Any, ...]: + """Alias for mark.args.""" + return self.mark.args + + @property + def kwargs(self) -> Mapping[str, Any]: + """Alias for mark.kwargs.""" + return self.mark.kwargs + + @property + def markname(self) -> str: + """:meta private:""" + return self.name # for backward-compat (2.4.1 had this attr) + + def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: + """Return a MarkDecorator with extra arguments added. + + Unlike calling the MarkDecorator, with_args() can be used even + if the sole argument is a callable/class. + """ + mark = Mark(self.name, args, kwargs, _ispytest=True) + return MarkDecorator(self.mark.combined_with(mark), _ispytest=True) + + # Type ignored because the overloads overlap with an incompatible + # return type. Not much we can do about that. Thankfully mypy picks + # the first match so it works out even if we break the rules. + @overload + def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] + pass + + @overload + def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: + pass + + def __call__(self, *args: object, **kwargs: object): + """Call the MarkDecorator.""" + if args and not kwargs: + func = args[0] + is_class = inspect.isclass(func) + if len(args) == 1 and (istestfunc(func) or is_class): + store_mark(func, self.mark, stacklevel=3) + return func + return self.with_args(*args, **kwargs) + + +def get_unpacked_marks( + obj: object | type, + *, + consider_mro: bool = True, +) -> list[Mark]: + """Obtain the unpacked marks that are stored on an object. + + If obj is a class and consider_mro is true, return marks applied to + this class and all of its super-classes in MRO order. If consider_mro + is false, only return marks applied directly to this class. + """ + if isinstance(obj, type): + if not consider_mro: + mark_lists = [obj.__dict__.get("pytestmark", [])] + else: + mark_lists = [ + x.__dict__.get("pytestmark", []) for x in reversed(obj.__mro__) + ] + mark_list = [] + for item in mark_lists: + if isinstance(item, list): + mark_list.extend(item) + else: + mark_list.append(item) + else: + mark_attribute = getattr(obj, "pytestmark", []) + if isinstance(mark_attribute, list): + mark_list = mark_attribute + else: + mark_list = [mark_attribute] + return list(normalize_mark_list(mark_list)) + + +def normalize_mark_list( + mark_list: Iterable[Mark | MarkDecorator], +) -> Iterable[Mark]: + """ + Normalize an iterable of Mark or MarkDecorator objects into a list of marks + by retrieving the `mark` attribute on MarkDecorator instances. + + :param mark_list: marks to normalize + :returns: A new list of the extracted Mark objects + """ + for mark in mark_list: + mark_obj = getattr(mark, "mark", mark) + if not isinstance(mark_obj, Mark): + raise TypeError(f"got {mark_obj!r} instead of Mark") + yield mark_obj + + +def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None: + """Store a Mark on an object. + + This is used to implement the Mark declarations/decorators correctly. + """ + assert isinstance(mark, Mark), mark + + from ..fixtures import getfixturemarker + + if getfixturemarker(obj) is not None: + warnings.warn(MARKED_FIXTURE, stacklevel=stacklevel) + + # Always reassign name to avoid updating pytestmark in a reference that + # was only borrowed. + obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark] + + +# Typing for builtin pytest marks. This is cheating; it gives builtin marks +# special privilege, and breaks modularity. But practicality beats purity... +if TYPE_CHECKING: + + class _SkipMarkDecorator(MarkDecorator): + @overload # type: ignore[override,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: ... + + @overload + def __call__(self, reason: str = ...) -> MarkDecorator: ... + + class _SkipifMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, + condition: str | bool = ..., + *conditions: str | bool, + reason: str = ..., + ) -> MarkDecorator: ... + + class _XfailMarkDecorator(MarkDecorator): + @overload # type: ignore[override,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: ... + + @overload + def __call__( + self, + condition: str | bool = False, + *conditions: str | bool, + reason: str = ..., + run: bool = ..., + raises: None | type[BaseException] | tuple[type[BaseException], ...] = ..., + strict: bool = ..., + ) -> MarkDecorator: ... + + class _ParametrizeMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + *, + indirect: bool | Sequence[str] = ..., + ids: Iterable[None | str | float | int | bool] + | Callable[[Any], object | None] + | None = ..., + scope: _ScopeName | None = ..., + ) -> MarkDecorator: ... + + class _UsefixturesMarkDecorator(MarkDecorator): + def __call__(self, *fixtures: str) -> MarkDecorator: # type: ignore[override] + ... + + class _FilterwarningsMarkDecorator(MarkDecorator): + def __call__(self, *filters: str) -> MarkDecorator: # type: ignore[override] + ... + + +@final +class MarkGenerator: + """Factory for :class:`MarkDecorator` objects - exposed as + a ``pytest.mark`` singleton instance. + + Example:: + + import pytest + + + @pytest.mark.slowtest + def test_function(): + pass + + applies a 'slowtest' :class:`Mark` on ``test_function``. + """ + + # See TYPE_CHECKING above. + if TYPE_CHECKING: + skip: _SkipMarkDecorator + skipif: _SkipifMarkDecorator + xfail: _XfailMarkDecorator + parametrize: _ParametrizeMarkDecorator + usefixtures: _UsefixturesMarkDecorator + filterwarnings: _FilterwarningsMarkDecorator + + def __init__(self, *, _ispytest: bool = False) -> None: + check_ispytest(_ispytest) + self._config: Config | None = None + self._markers: set[str] = set() + + def __getattr__(self, name: str) -> MarkDecorator: + """Generate a new :class:`MarkDecorator` with the given name.""" + if name[0] == "_": + raise AttributeError("Marker name must NOT start with underscore") + + if self._config is not None: + # We store a set of markers as a performance optimisation - if a mark + # name is in the set we definitely know it, but a mark may be known and + # not in the set. We therefore start by updating the set! + if name not in self._markers: + for line in self._config.getini("markers"): + # example lines: "skipif(condition): skip the given test if..." + # or "hypothesis: tests which use Hypothesis", so to get the + # marker name we split on both `:` and `(`. + marker = line.split(":")[0].split("(")[0].strip() + self._markers.add(marker) + + # If the name is not in the set of known marks after updating, + # then it really is time to issue a warning or an error. + if name not in self._markers: + if self._config.option.strict_markers or self._config.option.strict: + fail( + f"{name!r} not found in `markers` configuration option", + pytrace=False, + ) + + # Raise a specific error for common misspellings of "parametrize". + if name in ["parameterize", "parametrise", "parameterise"]: + __tracebackhide__ = True + fail(f"Unknown '{name}' mark, did you mean 'parametrize'?") + + warnings.warn( + f"Unknown pytest.mark.{name} - is this a typo? You can register " + "custom marks to avoid this warning - for details, see " + "https://docs.pytest.org/en/stable/how-to/mark.html", + PytestUnknownMarkWarning, + 2, + ) + + return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True) + + +MARK_GEN = MarkGenerator(_ispytest=True) + + +@final +class NodeKeywords(MutableMapping[str, Any]): + __slots__ = ("node", "parent", "_markers") + + def __init__(self, node: Node) -> None: + self.node = node + self.parent = node.parent + self._markers = {node.name: True} + + def __getitem__(self, key: str) -> Any: + try: + return self._markers[key] + except KeyError: + if self.parent is None: + raise + return self.parent.keywords[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._markers[key] = value + + # Note: we could've avoided explicitly implementing some of the methods + # below and use the collections.abc fallback, but that would be slow. + + def __contains__(self, key: object) -> bool: + return ( + key in self._markers + or self.parent is not None + and key in self.parent.keywords + ) + + def update( # type: ignore[override] + self, + other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), + **kwds: Any, + ) -> None: + self._markers.update(other) + self._markers.update(kwds) + + def __delitem__(self, key: str) -> None: + raise ValueError("cannot delete key in keywords dict") + + def __iter__(self) -> Iterator[str]: + # Doesn't need to be fast. + yield from self._markers + if self.parent is not None: + for keyword in self.parent.keywords: + # self._marks and self.parent.keywords can have duplicates. + if keyword not in self._markers: + yield keyword + + def __len__(self) -> int: + # Doesn't need to be fast. + return sum(1 for keyword in self) + + def __repr__(self) -> str: + return f"" diff --git a/.venv/Lib/site-packages/_pytest/monkeypatch.py b/.venv/Lib/site-packages/_pytest/monkeypatch.py new file mode 100644 index 0000000..46eb172 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/monkeypatch.py @@ -0,0 +1,415 @@ +# mypy: allow-untyped-defs +"""Monkeypatching and mocking functionality.""" + +from __future__ import annotations + +from contextlib import contextmanager +import os +import re +import sys +from typing import Any +from typing import final +from typing import Generator +from typing import Mapping +from typing import MutableMapping +from typing import overload +from typing import TypeVar +import warnings + +from _pytest.fixtures import fixture +from _pytest.warning_types import PytestWarning + + +RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") + + +K = TypeVar("K") +V = TypeVar("V") + + +@fixture +def monkeypatch() -> Generator[MonkeyPatch]: + """A convenient fixture for monkey-patching. + + The fixture provides these methods to modify objects, dictionaries, or + :data:`os.environ`: + + * :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` + * :meth:`monkeypatch.delattr(obj, name, raising=True) ` + * :meth:`monkeypatch.setitem(mapping, name, value) ` + * :meth:`monkeypatch.delitem(obj, name, raising=True) ` + * :meth:`monkeypatch.setenv(name, value, prepend=None) ` + * :meth:`monkeypatch.delenv(name, raising=True) ` + * :meth:`monkeypatch.syspath_prepend(path) ` + * :meth:`monkeypatch.chdir(path) ` + * :meth:`monkeypatch.context() ` + + All modifications will be undone after the requesting test function or + fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` + or :class:`AttributeError` will be raised if the set/deletion operation does not have the + specified target. + + To undo modifications done by the fixture in a contained scope, + use :meth:`context() `. + """ + mpatch = MonkeyPatch() + yield mpatch + mpatch.undo() + + +def resolve(name: str) -> object: + # Simplified from zope.dottedname. + parts = name.split(".") + + used = parts.pop(0) + found: object = __import__(used) + for part in parts: + used += "." + part + try: + found = getattr(found, part) + except AttributeError: + pass + else: + continue + # We use explicit un-nesting of the handling block in order + # to avoid nested exceptions. + try: + __import__(used) + except ImportError as ex: + expected = str(ex).split()[-1] + if expected == used: + raise + else: + raise ImportError(f"import error in {used}: {ex}") from ex + found = annotated_getattr(found, part, used) + return found + + +def annotated_getattr(obj: object, name: str, ann: str) -> object: + try: + obj = getattr(obj, name) + except AttributeError as e: + raise AttributeError( + f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}" + ) from e + return obj + + +def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]: + if not isinstance(import_path, str) or "." not in import_path: + raise TypeError(f"must be absolute import path string, not {import_path!r}") + module, attr = import_path.rsplit(".", 1) + target = resolve(module) + if raising: + annotated_getattr(target, attr, ann=module) + return attr, target + + +class Notset: + def __repr__(self) -> str: + return "" + + +notset = Notset() + + +@final +class MonkeyPatch: + """Helper to conveniently monkeypatch attributes/items/environment + variables/syspath. + + Returned by the :fixture:`monkeypatch` fixture. + + .. versionchanged:: 6.2 + Can now also be used directly as `pytest.MonkeyPatch()`, for when + the fixture is not available. In this case, use + :meth:`with MonkeyPatch.context() as mp: ` or remember to call + :meth:`undo` explicitly. + """ + + def __init__(self) -> None: + self._setattr: list[tuple[object, str, object]] = [] + self._setitem: list[tuple[Mapping[Any, Any], object, object]] = [] + self._cwd: str | None = None + self._savesyspath: list[str] | None = None + + @classmethod + @contextmanager + def context(cls) -> Generator[MonkeyPatch]: + """Context manager that returns a new :class:`MonkeyPatch` object + which undoes any patching done inside the ``with`` block upon exit. + + Example: + + .. code-block:: python + + import functools + + + def test_partial(monkeypatch): + with monkeypatch.context() as m: + m.setattr(functools, "partial", 3) + + Useful in situations where it is desired to undo some patches before the test ends, + such as mocking ``stdlib`` functions that might break pytest itself if mocked (for examples + of this see :issue:`3290`). + """ + m = cls() + try: + yield m + finally: + m.undo() + + @overload + def setattr( + self, + target: str, + name: object, + value: Notset = ..., + raising: bool = ..., + ) -> None: ... + + @overload + def setattr( + self, + target: object, + name: str, + value: object, + raising: bool = ..., + ) -> None: ... + + def setattr( + self, + target: str | object, + name: object | str, + value: object = notset, + raising: bool = True, + ) -> None: + """ + Set attribute value on target, memorizing the old value. + + For example: + + .. code-block:: python + + import os + + monkeypatch.setattr(os, "getcwd", lambda: "/") + + The code above replaces the :func:`os.getcwd` function by a ``lambda`` which + always returns ``"/"``. + + For convenience, you can specify a string as ``target`` which + will be interpreted as a dotted import path, with the last part + being the attribute name: + + .. code-block:: python + + monkeypatch.setattr("os.getcwd", lambda: "/") + + Raises :class:`AttributeError` if the attribute does not exist, unless + ``raising`` is set to False. + + **Where to patch** + + ``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one. + There can be many names pointing to any individual object, so for patching to work you must ensure + that you patch the name used by the system under test. + + See the section :ref:`Where to patch ` in the :mod:`unittest.mock` + docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but + applies to ``monkeypatch.setattr`` as well. + """ + __tracebackhide__ = True + import inspect + + if isinstance(value, Notset): + if not isinstance(target, str): + raise TypeError( + "use setattr(target, name, value) or " + "setattr(target, value) with target being a dotted " + "import string" + ) + value = name + name, target = derive_importpath(target, raising) + else: + if not isinstance(name, str): + raise TypeError( + "use setattr(target, name, value) with name being a string or " + "setattr(target, value) with target being a dotted " + "import string" + ) + + oldval = getattr(target, name, notset) + if raising and oldval is notset: + raise AttributeError(f"{target!r} has no attribute {name!r}") + + # avoid class descriptors like staticmethod/classmethod + if inspect.isclass(target): + oldval = target.__dict__.get(name, notset) + self._setattr.append((target, name, oldval)) + setattr(target, name, value) + + def delattr( + self, + target: object | str, + name: str | Notset = notset, + raising: bool = True, + ) -> None: + """Delete attribute ``name`` from ``target``. + + If no ``name`` is specified and ``target`` is a string + it will be interpreted as a dotted import path with the + last part being the attribute name. + + Raises AttributeError it the attribute does not exist, unless + ``raising`` is set to False. + """ + __tracebackhide__ = True + import inspect + + if isinstance(name, Notset): + if not isinstance(target, str): + raise TypeError( + "use delattr(target, name) or " + "delattr(target) with target being a dotted " + "import string" + ) + name, target = derive_importpath(target, raising) + + if not hasattr(target, name): + if raising: + raise AttributeError(name) + else: + oldval = getattr(target, name, notset) + # Avoid class descriptors like staticmethod/classmethod. + if inspect.isclass(target): + oldval = target.__dict__.get(name, notset) + self._setattr.append((target, name, oldval)) + delattr(target, name) + + def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None: + """Set dictionary entry ``name`` to value.""" + self._setitem.append((dic, name, dic.get(name, notset))) + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + dic[name] = value # type: ignore[index] + + def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: + """Delete ``name`` from dict. + + Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to + False. + """ + if name not in dic: + if raising: + raise KeyError(name) + else: + self._setitem.append((dic, name, dic.get(name, notset))) + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + del dic[name] # type: ignore[attr-defined] + + def setenv(self, name: str, value: str, prepend: str | None = None) -> None: + """Set environment variable ``name`` to ``value``. + + If ``prepend`` is a character, read the current environment variable + value and prepend the ``value`` adjoined with the ``prepend`` + character. + """ + if not isinstance(value, str): + warnings.warn( # type: ignore[unreachable] + PytestWarning( + f"Value of environment variable {name} type should be str, but got " + f"{value!r} (type: {type(value).__name__}); converted to str implicitly" + ), + stacklevel=2, + ) + value = str(value) + if prepend and name in os.environ: + value = value + prepend + os.environ[name] + self.setitem(os.environ, name, value) + + def delenv(self, name: str, raising: bool = True) -> None: + """Delete ``name`` from the environment. + + Raises ``KeyError`` if it does not exist, unless ``raising`` is set to + False. + """ + environ: MutableMapping[str, str] = os.environ + self.delitem(environ, name, raising=raising) + + def syspath_prepend(self, path) -> None: + """Prepend ``path`` to ``sys.path`` list of import locations.""" + if self._savesyspath is None: + self._savesyspath = sys.path[:] + sys.path.insert(0, str(path)) + + # https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171 + # this is only needed when pkg_resources was already loaded by the namespace package + if "pkg_resources" in sys.modules: + from pkg_resources import fixup_namespace_packages + + fixup_namespace_packages(str(path)) + + # A call to syspathinsert() usually means that the caller wants to + # import some dynamically created files, thus with python3 we + # invalidate its import caches. + # This is especially important when any namespace package is in use, + # since then the mtime based FileFinder cache (that gets created in + # this case already) gets not invalidated when writing the new files + # quickly afterwards. + from importlib import invalidate_caches + + invalidate_caches() + + def chdir(self, path: str | os.PathLike[str]) -> None: + """Change the current working directory to the specified path. + + :param path: + The path to change into. + """ + if self._cwd is None: + self._cwd = os.getcwd() + os.chdir(path) + + def undo(self) -> None: + """Undo previous changes. + + This call consumes the undo stack. Calling it a second time has no + effect unless you do more monkeypatching after the undo call. + + There is generally no need to call `undo()`, since it is + called automatically during tear-down. + + .. note:: + The same `monkeypatch` fixture is used across a + single test function invocation. If `monkeypatch` is used both by + the test function itself and one of the test fixtures, + calling `undo()` will undo all of the changes made in + both functions. + + Prefer to use :meth:`context() ` instead. + """ + for obj, name, value in reversed(self._setattr): + if value is not notset: + setattr(obj, name, value) + else: + delattr(obj, name) + self._setattr[:] = [] + for dictionary, key, value in reversed(self._setitem): + if value is notset: + try: + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + del dictionary[key] # type: ignore[attr-defined] + except KeyError: + pass # Was already deleted, so we have the desired state. + else: + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + dictionary[key] = value # type: ignore[index] + self._setitem[:] = [] + if self._savesyspath is not None: + sys.path[:] = self._savesyspath + self._savesyspath = None + + if self._cwd is not None: + os.chdir(self._cwd) + self._cwd = None diff --git a/.venv/Lib/site-packages/_pytest/nodes.py b/.venv/Lib/site-packages/_pytest/nodes.py new file mode 100644 index 0000000..51bc517 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/nodes.py @@ -0,0 +1,766 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import abc +from functools import cached_property +from inspect import signature +import os +import pathlib +from pathlib import Path +from typing import Any +from typing import Callable +from typing import cast +from typing import Iterable +from typing import Iterator +from typing import MutableMapping +from typing import NoReturn +from typing import overload +from typing import TYPE_CHECKING +from typing import TypeVar +import warnings + +import pluggy + +import _pytest._code +from _pytest._code import getfslineno +from _pytest._code.code import ExceptionInfo +from _pytest._code.code import TerminalRepr +from _pytest._code.code import Traceback +from _pytest._code.code import TracebackStyle +from _pytest.compat import LEGACY_PATH +from _pytest.config import Config +from _pytest.config import ConftestImportFailure +from _pytest.config.compat import _check_path +from _pytest.deprecated import NODE_CTOR_FSPATH_ARG +from _pytest.mark.structures import Mark +from _pytest.mark.structures import MarkDecorator +from _pytest.mark.structures import NodeKeywords +from _pytest.outcomes import fail +from _pytest.pathlib import absolutepath +from _pytest.pathlib import commonpath +from _pytest.stash import Stash +from _pytest.warning_types import PytestWarning + + +if TYPE_CHECKING: + from typing_extensions import Self + + # Imported here due to circular import. + from _pytest.main import Session + + +SEP = "/" + +tracebackcutdir = Path(_pytest.__file__).parent + + +_T = TypeVar("_T") + + +def _imply_path( + node_type: type[Node], + path: Path | None, + fspath: LEGACY_PATH | None, +) -> Path: + if fspath is not None: + warnings.warn( + NODE_CTOR_FSPATH_ARG.format( + node_type_name=node_type.__name__, + ), + stacklevel=6, + ) + if path is not None: + if fspath is not None: + _check_path(path, fspath) + return path + else: + assert fspath is not None + return Path(fspath) + + +_NodeType = TypeVar("_NodeType", bound="Node") + + +class NodeMeta(abc.ABCMeta): + """Metaclass used by :class:`Node` to enforce that direct construction raises + :class:`Failed`. + + This behaviour supports the indirection introduced with :meth:`Node.from_parent`, + the named constructor to be used instead of direct construction. The design + decision to enforce indirection with :class:`NodeMeta` was made as a + temporary aid for refactoring the collection tree, which was diagnosed to + have :class:`Node` objects whose creational patterns were overly entangled. + Once the refactoring is complete, this metaclass can be removed. + + See https://github.com/pytest-dev/pytest/projects/3 for an overview of the + progress on detangling the :class:`Node` classes. + """ + + def __call__(cls, *k, **kw) -> NoReturn: + msg = ( + "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" + "See " + "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" + " for more details." + ).format(name=f"{cls.__module__}.{cls.__name__}") + fail(msg, pytrace=False) + + def _create(cls: type[_T], *k, **kw) -> _T: + try: + return super().__call__(*k, **kw) # type: ignore[no-any-return,misc] + except TypeError: + sig = signature(getattr(cls, "__init__")) + known_kw = {k: v for k, v in kw.items() if k in sig.parameters} + from .warning_types import PytestDeprecationWarning + + warnings.warn( + PytestDeprecationWarning( + f"{cls} is not using a cooperative constructor and only takes {set(known_kw)}.\n" + "See https://docs.pytest.org/en/stable/deprecations.html" + "#constructors-of-custom-pytest-node-subclasses-should-take-kwargs " + "for more details." + ) + ) + + return super().__call__(*k, **known_kw) # type: ignore[no-any-return,misc] + + +class Node(abc.ABC, metaclass=NodeMeta): + r"""Base class of :class:`Collector` and :class:`Item`, the components of + the test collection tree. + + ``Collector``\'s are the internal nodes of the tree, and ``Item``\'s are the + leaf nodes. + """ + + # Implemented in the legacypath plugin. + #: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage + #: for methods not migrated to ``pathlib.Path`` yet, such as + #: :meth:`Item.reportinfo `. Will be deprecated in + #: a future release, prefer using :attr:`path` instead. + fspath: LEGACY_PATH + + # Use __slots__ to make attribute access faster. + # Note that __dict__ is still available. + __slots__ = ( + "name", + "parent", + "config", + "session", + "path", + "_nodeid", + "_store", + "__dict__", + ) + + def __init__( + self, + name: str, + parent: Node | None = None, + config: Config | None = None, + session: Session | None = None, + fspath: LEGACY_PATH | None = None, + path: Path | None = None, + nodeid: str | None = None, + ) -> None: + #: A unique name within the scope of the parent node. + self.name: str = name + + #: The parent collector node. + self.parent = parent + + if config: + #: The pytest config object. + self.config: Config = config + else: + if not parent: + raise TypeError("config or parent must be provided") + self.config = parent.config + + if session: + #: The pytest session this node is part of. + self.session: Session = session + else: + if not parent: + raise TypeError("session or parent must be provided") + self.session = parent.session + + if path is None and fspath is None: + path = getattr(parent, "path", None) + #: Filesystem path where this node was collected from (can be None). + self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath) + + # The explicit annotation is to avoid publicly exposing NodeKeywords. + #: Keywords/markers collected from all scopes. + self.keywords: MutableMapping[str, Any] = NodeKeywords(self) + + #: The marker objects belonging to this node. + self.own_markers: list[Mark] = [] + + #: Allow adding of extra keywords to use for matching. + self.extra_keyword_matches: set[str] = set() + + if nodeid is not None: + assert "::()" not in nodeid + self._nodeid = nodeid + else: + if not self.parent: + raise TypeError("nodeid or parent must be provided") + self._nodeid = self.parent.nodeid + "::" + self.name + + #: A place where plugins can store information on the node for their + #: own use. + self.stash: Stash = Stash() + # Deprecated alias. Was never public. Can be removed in a few releases. + self._store = self.stash + + @classmethod + def from_parent(cls, parent: Node, **kw) -> Self: + """Public constructor for Nodes. + + This indirection got introduced in order to enable removing + the fragile logic from the node constructors. + + Subclasses can use ``super().from_parent(...)`` when overriding the + construction. + + :param parent: The parent node of this Node. + """ + if "config" in kw: + raise TypeError("config is not a valid argument for from_parent") + if "session" in kw: + raise TypeError("session is not a valid argument for from_parent") + return cls._create(parent=parent, **kw) + + @property + def ihook(self) -> pluggy.HookRelay: + """fspath-sensitive hook proxy used to call pytest hooks.""" + return self.session.gethookproxy(self.path) + + def __repr__(self) -> str: + return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None)) + + def warn(self, warning: Warning) -> None: + """Issue a warning for this Node. + + Warnings will be displayed after the test session, unless explicitly suppressed. + + :param Warning warning: + The warning instance to issue. + + :raises ValueError: If ``warning`` instance is not a subclass of Warning. + + Example usage: + + .. code-block:: python + + node.warn(PytestWarning("some message")) + node.warn(UserWarning("some message")) + + .. versionchanged:: 6.2 + Any subclass of :class:`Warning` is now accepted, rather than only + :class:`PytestWarning ` subclasses. + """ + # enforce type checks here to avoid getting a generic type error later otherwise. + if not isinstance(warning, Warning): + raise ValueError( + f"warning must be an instance of Warning or subclass, got {warning!r}" + ) + path, lineno = get_fslocation_from_item(self) + assert lineno is not None + warnings.warn_explicit( + warning, + category=None, + filename=str(path), + lineno=lineno + 1, + ) + + # Methods for ordering nodes. + + @property + def nodeid(self) -> str: + """A ::-separated string denoting its collection tree address.""" + return self._nodeid + + def __hash__(self) -> int: + return hash(self._nodeid) + + def setup(self) -> None: + pass + + def teardown(self) -> None: + pass + + def iter_parents(self) -> Iterator[Node]: + """Iterate over all parent collectors starting from and including self + up to the root of the collection tree. + + .. versionadded:: 8.1 + """ + parent: Node | None = self + while parent is not None: + yield parent + parent = parent.parent + + def listchain(self) -> list[Node]: + """Return a list of all parent collectors starting from the root of the + collection tree down to and including self.""" + chain = [] + item: Node | None = self + while item is not None: + chain.append(item) + item = item.parent + chain.reverse() + return chain + + def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None: + """Dynamically add a marker object to the node. + + :param marker: + The marker. + :param append: + Whether to append the marker, or prepend it. + """ + from _pytest.mark import MARK_GEN + + if isinstance(marker, MarkDecorator): + marker_ = marker + elif isinstance(marker, str): + marker_ = getattr(MARK_GEN, marker) + else: + raise ValueError("is not a string or pytest.mark.* Marker") + self.keywords[marker_.name] = marker_ + if append: + self.own_markers.append(marker_.mark) + else: + self.own_markers.insert(0, marker_.mark) + + def iter_markers(self, name: str | None = None) -> Iterator[Mark]: + """Iterate over all markers of the node. + + :param name: If given, filter the results by the name attribute. + :returns: An iterator of the markers of the node. + """ + return (x[1] for x in self.iter_markers_with_node(name=name)) + + def iter_markers_with_node( + self, name: str | None = None + ) -> Iterator[tuple[Node, Mark]]: + """Iterate over all markers of the node. + + :param name: If given, filter the results by the name attribute. + :returns: An iterator of (node, mark) tuples. + """ + for node in self.iter_parents(): + for mark in node.own_markers: + if name is None or getattr(mark, "name", None) == name: + yield node, mark + + @overload + def get_closest_marker(self, name: str) -> Mark | None: ... + + @overload + def get_closest_marker(self, name: str, default: Mark) -> Mark: ... + + def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None: + """Return the first marker matching the name, from closest (for + example function) to farther level (for example module level). + + :param default: Fallback return value if no marker was found. + :param name: Name to filter by. + """ + return next(self.iter_markers(name=name), default) + + def listextrakeywords(self) -> set[str]: + """Return a set of all extra keywords in self and any parents.""" + extra_keywords: set[str] = set() + for item in self.listchain(): + extra_keywords.update(item.extra_keyword_matches) + return extra_keywords + + def listnames(self) -> list[str]: + return [x.name for x in self.listchain()] + + def addfinalizer(self, fin: Callable[[], object]) -> None: + """Register a function to be called without arguments when this node is + finalized. + + This method can only be called when this node is active + in a setup chain, for example during self.setup(). + """ + self.session._setupstate.addfinalizer(fin, self) + + def getparent(self, cls: type[_NodeType]) -> _NodeType | None: + """Get the closest parent node (including self) which is an instance of + the given class. + + :param cls: The node class to search for. + :returns: The node, if found. + """ + for node in self.iter_parents(): + if isinstance(node, cls): + return node + return None + + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: + return excinfo.traceback + + def _repr_failure_py( + self, + excinfo: ExceptionInfo[BaseException], + style: TracebackStyle | None = None, + ) -> TerminalRepr: + from _pytest.fixtures import FixtureLookupError + + if isinstance(excinfo.value, ConftestImportFailure): + excinfo = ExceptionInfo.from_exception(excinfo.value.cause) + if isinstance(excinfo.value, fail.Exception): + if not excinfo.value.pytrace: + style = "value" + if isinstance(excinfo.value, FixtureLookupError): + return excinfo.value.formatrepr() + + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] + if self.config.getoption("fulltrace", False): + style = "long" + tbfilter = False + else: + tbfilter = self._traceback_filter + if style == "auto": + style = "long" + # XXX should excinfo.getrepr record all data and toterminal() process it? + if style is None: + if self.config.getoption("tbstyle", "auto") == "short": + style = "short" + else: + style = "long" + + if self.config.get_verbosity() > 1: + truncate_locals = False + else: + truncate_locals = True + + truncate_args = False if self.config.get_verbosity() > 2 else True + + # excinfo.getrepr() formats paths relative to the CWD if `abspath` is False. + # It is possible for a fixture/test to change the CWD while this code runs, which + # would then result in the user seeing confusing paths in the failure message. + # To fix this, if the CWD changed, always display the full absolute path. + # It will be better to just always display paths relative to invocation_dir, but + # this requires a lot of plumbing (#6428). + try: + abspath = Path(os.getcwd()) != self.config.invocation_params.dir + except OSError: + abspath = True + + return excinfo.getrepr( + funcargs=True, + abspath=abspath, + showlocals=self.config.getoption("showlocals", False), + style=style, + tbfilter=tbfilter, + truncate_locals=truncate_locals, + truncate_args=truncate_args, + ) + + def repr_failure( + self, + excinfo: ExceptionInfo[BaseException], + style: TracebackStyle | None = None, + ) -> str | TerminalRepr: + """Return a representation of a collection or test failure. + + .. seealso:: :ref:`non-python tests` + + :param excinfo: Exception information for the failure. + """ + return self._repr_failure_py(excinfo, style) + + +def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]: + """Try to extract the actual location from a node, depending on available attributes: + + * "location": a pair (path, lineno) + * "obj": a Python object that the node wraps. + * "path": just a path + + :rtype: A tuple of (str|Path, int) with filename and 0-based line number. + """ + # See Item.location. + location: tuple[str, int | None, str] | None = getattr(node, "location", None) + if location is not None: + return location[:2] + obj = getattr(node, "obj", None) + if obj is not None: + return getfslineno(obj) + return getattr(node, "path", "unknown location"), -1 + + +class Collector(Node, abc.ABC): + """Base class of all collectors. + + Collector create children through `collect()` and thus iteratively build + the collection tree. + """ + + class CollectError(Exception): + """An error during collection, contains a custom message.""" + + @abc.abstractmethod + def collect(self) -> Iterable[Item | Collector]: + """Collect children (items and collectors) for this collector.""" + raise NotImplementedError("abstract") + + # TODO: This omits the style= parameter which breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, excinfo: ExceptionInfo[BaseException] + ) -> str | TerminalRepr: + """Return a representation of a collection failure. + + :param excinfo: Exception information for the failure. + """ + if isinstance(excinfo.value, self.CollectError) and not self.config.getoption( + "fulltrace", False + ): + exc = excinfo.value + return str(exc.args[0]) + + # Respect explicit tbstyle option, but default to "short" + # (_repr_failure_py uses "long" with "fulltrace" option always). + tbstyle = self.config.getoption("tbstyle", "auto") + if tbstyle == "auto": + tbstyle = "short" + + return self._repr_failure_py(excinfo, style=tbstyle) + + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: + if hasattr(self, "path"): + traceback = excinfo.traceback + ntraceback = traceback.cut(path=self.path) + if ntraceback == traceback: + ntraceback = ntraceback.cut(excludepath=tracebackcutdir) + return ntraceback.filter(excinfo) + return excinfo.traceback + + +def _check_initialpaths_for_relpath(session: Session, path: Path) -> str | None: + for initial_path in session._initialpaths: + if commonpath(path, initial_path) == initial_path: + rel = str(path.relative_to(initial_path)) + return "" if rel == "." else rel + return None + + +class FSCollector(Collector, abc.ABC): + """Base class for filesystem collectors.""" + + def __init__( + self, + fspath: LEGACY_PATH | None = None, + path_or_parent: Path | Node | None = None, + path: Path | None = None, + name: str | None = None, + parent: Node | None = None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, + ) -> None: + if path_or_parent: + if isinstance(path_or_parent, Node): + assert parent is None + parent = cast(FSCollector, path_or_parent) + elif isinstance(path_or_parent, Path): + assert path is None + path = path_or_parent + + path = _imply_path(type(self), path, fspath=fspath) + if name is None: + name = path.name + if parent is not None and parent.path != path: + try: + rel = path.relative_to(parent.path) + except ValueError: + pass + else: + name = str(rel) + name = name.replace(os.sep, SEP) + self.path = path + + if session is None: + assert parent is not None + session = parent.session + + if nodeid is None: + try: + nodeid = str(self.path.relative_to(session.config.rootpath)) + except ValueError: + nodeid = _check_initialpaths_for_relpath(session, path) + + if nodeid and os.sep != SEP: + nodeid = nodeid.replace(os.sep, SEP) + + super().__init__( + name=name, + parent=parent, + config=config, + session=session, + nodeid=nodeid, + path=path, + ) + + @classmethod + def from_parent( + cls, + parent, + *, + fspath: LEGACY_PATH | None = None, + path: Path | None = None, + **kw, + ) -> Self: + """The public constructor.""" + return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) + + +class File(FSCollector, abc.ABC): + """Base class for collecting tests from a file. + + :ref:`non-python tests`. + """ + + +class Directory(FSCollector, abc.ABC): + """Base class for collecting files from a directory. + + A basic directory collector does the following: goes over the files and + sub-directories in the directory and creates collectors for them by calling + the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`, + after checking that they are not ignored using + :hook:`pytest_ignore_collect`. + + The default directory collectors are :class:`~pytest.Dir` and + :class:`~pytest.Package`. + + .. versionadded:: 8.0 + + :ref:`custom directory collectors`. + """ + + +class Item(Node, abc.ABC): + """Base class of all test invocation items. + + Note that for a single function there might be multiple test invocation items. + """ + + nextitem = None + + def __init__( + self, + name, + parent=None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, + **kw, + ) -> None: + # The first two arguments are intentionally passed positionally, + # to keep plugins who define a node type which inherits from + # (pytest.Item, pytest.File) working (see issue #8435). + # They can be made kwargs when the deprecation above is done. + super().__init__( + name, + parent, + config=config, + session=session, + nodeid=nodeid, + **kw, + ) + self._report_sections: list[tuple[str, str, str]] = [] + + #: A list of tuples (name, value) that holds user defined properties + #: for this test. + self.user_properties: list[tuple[str, object]] = [] + + self._check_item_and_collector_diamond_inheritance() + + def _check_item_and_collector_diamond_inheritance(self) -> None: + """ + Check if the current type inherits from both File and Collector + at the same time, emitting a warning accordingly (#8447). + """ + cls = type(self) + + # We inject an attribute in the type to avoid issuing this warning + # for the same class more than once, which is not helpful. + # It is a hack, but was deemed acceptable in order to avoid + # flooding the user in the common case. + attr_name = "_pytest_diamond_inheritance_warning_shown" + if getattr(cls, attr_name, False): + return + setattr(cls, attr_name, True) + + problems = ", ".join( + base.__name__ for base in cls.__bases__ if issubclass(base, Collector) + ) + if problems: + warnings.warn( + f"{cls.__name__} is an Item subclass and should not be a collector, " + f"however its bases {problems} are collectors.\n" + "Please split the Collectors and the Item into separate node types.\n" + "Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n" + "example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/", + PytestWarning, + ) + + @abc.abstractmethod + def runtest(self) -> None: + """Run the test case for this item. + + Must be implemented by subclasses. + + .. seealso:: :ref:`non-python tests` + """ + raise NotImplementedError("runtest must be implemented by Item subclass") + + def add_report_section(self, when: str, key: str, content: str) -> None: + """Add a new report section, similar to what's done internally to add + stdout and stderr captured output:: + + item.add_report_section("call", "stdout", "report section contents") + + :param str when: + One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``. + :param str key: + Name of the section, can be customized at will. Pytest uses ``"stdout"`` and + ``"stderr"`` internally. + :param str content: + The full contents as a string. + """ + if content: + self._report_sections.append((when, key, content)) + + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: + """Get location information for this item for test reports. + + Returns a tuple with three elements: + + - The path of the test (default ``self.path``) + - The 0-based line number of the test (default ``None``) + - A name of the test to be shown (default ``""``) + + .. seealso:: :ref:`non-python tests` + """ + return self.path, None, "" + + @cached_property + def location(self) -> tuple[str, int | None, str]: + """ + Returns a tuple of ``(relfspath, lineno, testname)`` for this item + where ``relfspath`` is file path relative to ``config.rootpath`` + and lineno is a 0-based line number. + """ + location = self.reportinfo() + path = absolutepath(location[0]) + relfspath = self.session._node_location_to_relpath(path) + assert type(location[2]) is str + return (relfspath, location[1], location[2]) diff --git a/.venv/Lib/site-packages/_pytest/outcomes.py b/.venv/Lib/site-packages/_pytest/outcomes.py new file mode 100644 index 0000000..5b20803 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/outcomes.py @@ -0,0 +1,318 @@ +"""Exception classes and constants handling test outcomes as well as +functions creating them.""" + +from __future__ import annotations + +import sys +from typing import Any +from typing import Callable +from typing import cast +from typing import NoReturn +from typing import Protocol +from typing import Type +from typing import TypeVar + +from .warning_types import PytestDeprecationWarning + + +class OutcomeException(BaseException): + """OutcomeException and its subclass instances indicate and contain info + about test and collection outcomes.""" + + def __init__(self, msg: str | None = None, pytrace: bool = True) -> None: + if msg is not None and not isinstance(msg, str): + error_msg = ( # type: ignore[unreachable] + "{} expected string as 'msg' parameter, got '{}' instead.\n" + "Perhaps you meant to use a mark?" + ) + raise TypeError(error_msg.format(type(self).__name__, type(msg).__name__)) + super().__init__(msg) + self.msg = msg + self.pytrace = pytrace + + def __repr__(self) -> str: + if self.msg is not None: + return self.msg + return f"<{self.__class__.__name__} instance>" + + __str__ = __repr__ + + +TEST_OUTCOME = (OutcomeException, Exception) + + +class Skipped(OutcomeException): + # XXX hackish: on 3k we fake to live in the builtins + # in order to have Skipped exception printing shorter/nicer + __module__ = "builtins" + + def __init__( + self, + msg: str | None = None, + pytrace: bool = True, + allow_module_level: bool = False, + *, + _use_item_location: bool = False, + ) -> None: + super().__init__(msg=msg, pytrace=pytrace) + self.allow_module_level = allow_module_level + # If true, the skip location is reported as the item's location, + # instead of the place that raises the exception/calls skip(). + self._use_item_location = _use_item_location + + +class Failed(OutcomeException): + """Raised from an explicit call to pytest.fail().""" + + __module__ = "builtins" + + +class Exit(Exception): + """Raised for immediate program exits (no tracebacks/summaries).""" + + def __init__( + self, msg: str = "unknown reason", returncode: int | None = None + ) -> None: + self.msg = msg + self.returncode = returncode + super().__init__(msg) + + +# Elaborate hack to work around https://github.com/python/mypy/issues/2087. +# Ideally would just be `exit.Exception = Exit` etc. + +_F = TypeVar("_F", bound=Callable[..., object]) +_ET = TypeVar("_ET", bound=Type[BaseException]) + + +class _WithException(Protocol[_F, _ET]): + Exception: _ET + __call__: _F + + +def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _ET]]: + def decorate(func: _F) -> _WithException[_F, _ET]: + func_with_exception = cast(_WithException[_F, _ET], func) + func_with_exception.Exception = exception_type + return func_with_exception + + return decorate + + +# Exposed helper methods. + + +@_with_exception(Exit) +def exit( + reason: str = "", + returncode: int | None = None, +) -> NoReturn: + """Exit testing process. + + :param reason: + The message to show as the reason for exiting pytest. reason has a default value + only because `msg` is deprecated. + + :param returncode: + Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`. + + :raises pytest.exit.Exception: + The exception that is raised. + """ + __tracebackhide__ = True + raise Exit(reason, returncode) + + +@_with_exception(Skipped) +def skip( + reason: str = "", + *, + allow_module_level: bool = False, +) -> NoReturn: + """Skip an executing test with the given message. + + This function should be called only during testing (setup, call or teardown) or + during collection by using the ``allow_module_level`` flag. This function can + be called in doctests as well. + + :param reason: + The message to show the user as reason for the skip. + + :param allow_module_level: + Allows this function to be called at module level. + Raising the skip exception at module level will stop + the execution of the module and prevent the collection of all tests in the module, + even those defined before the `skip` call. + + Defaults to False. + + :raises pytest.skip.Exception: + The exception that is raised. + + .. note:: + It is better to use the :ref:`pytest.mark.skipif ref` marker when + possible to declare a test to be skipped under certain conditions + like mismatching platforms or dependencies. + Similarly, use the ``# doctest: +SKIP`` directive (see :py:data:`doctest.SKIP`) + to skip a doctest statically. + """ + __tracebackhide__ = True + raise Skipped(msg=reason, allow_module_level=allow_module_level) + + +@_with_exception(Failed) +def fail(reason: str = "", pytrace: bool = True) -> NoReturn: + """Explicitly fail an executing test with the given message. + + :param reason: + The message to show the user as reason for the failure. + + :param pytrace: + If False, msg represents the full failure information and no + python traceback will be reported. + + :raises pytest.fail.Exception: + The exception that is raised. + """ + __tracebackhide__ = True + raise Failed(msg=reason, pytrace=pytrace) + + +class XFailed(Failed): + """Raised from an explicit call to pytest.xfail().""" + + +@_with_exception(XFailed) +def xfail(reason: str = "") -> NoReturn: + """Imperatively xfail an executing test or setup function with the given reason. + + This function should be called only during testing (setup, call or teardown). + + No other code is executed after using ``xfail()`` (it is implemented + internally by raising an exception). + + :param reason: + The message to show the user as reason for the xfail. + + .. note:: + It is better to use the :ref:`pytest.mark.xfail ref` marker when + possible to declare a test to be xfailed under certain conditions + like known bugs or missing features. + + :raises pytest.xfail.Exception: + The exception that is raised. + """ + __tracebackhide__ = True + raise XFailed(reason) + + +def importorskip( + modname: str, + minversion: str | None = None, + reason: str | None = None, + *, + exc_type: type[ImportError] | None = None, +) -> Any: + """Import and return the requested module ``modname``, or skip the + current test if the module cannot be imported. + + :param modname: + The name of the module to import. + :param minversion: + If given, the imported module's ``__version__`` attribute must be at + least this minimal version, otherwise the test is still skipped. + :param reason: + If given, this reason is shown as the message when the module cannot + be imported. + :param exc_type: + The exception that should be captured in order to skip modules. + Must be :py:class:`ImportError` or a subclass. + + If the module can be imported but raises :class:`ImportError`, pytest will + issue a warning to the user, as often users expect the module not to be + found (which would raise :class:`ModuleNotFoundError` instead). + + This warning can be suppressed by passing ``exc_type=ImportError`` explicitly. + + See :ref:`import-or-skip-import-error` for details. + + + :returns: + The imported module. This should be assigned to its canonical name. + + :raises pytest.skip.Exception: + If the module cannot be imported. + + Example:: + + docutils = pytest.importorskip("docutils") + + .. versionadded:: 8.2 + + The ``exc_type`` parameter. + """ + import warnings + + __tracebackhide__ = True + compile(modname, "", "eval") # to catch syntaxerrors + + # Until pytest 9.1, we will warn the user if we catch ImportError (instead of ModuleNotFoundError), + # as this might be hiding an installation/environment problem, which is not usually what is intended + # when using importorskip() (#11523). + # In 9.1, to keep the function signature compatible, we just change the code below to: + # 1. Use `exc_type = ModuleNotFoundError` if `exc_type` is not given. + # 2. Remove `warn_on_import` and the warning handling. + if exc_type is None: + exc_type = ImportError + warn_on_import_error = True + else: + warn_on_import_error = False + + skipped: Skipped | None = None + warning: Warning | None = None + + with warnings.catch_warnings(): + # Make sure to ignore ImportWarnings that might happen because + # of existing directories with the same name we're trying to + # import but without a __init__.py file. + warnings.simplefilter("ignore") + + try: + __import__(modname) + except exc_type as exc: + # Do not raise or issue warnings inside the catch_warnings() block. + if reason is None: + reason = f"could not import {modname!r}: {exc}" + skipped = Skipped(reason, allow_module_level=True) + + if warn_on_import_error and not isinstance(exc, ModuleNotFoundError): + lines = [ + "", + f"Module '{modname}' was found, but when imported by pytest it raised:", + f" {exc!r}", + "In pytest 9.1 this warning will become an error by default.", + "You can fix the underlying problem, or alternatively overwrite this behavior and silence this " + "warning by passing exc_type=ImportError explicitly.", + "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror", + ] + warning = PytestDeprecationWarning("\n".join(lines)) + + if warning: + warnings.warn(warning, stacklevel=2) + if skipped: + raise skipped + + mod = sys.modules[modname] + if minversion is None: + return mod + verattr = getattr(mod, "__version__", None) + if minversion is not None: + # Imported lazily to improve start-up time. + from packaging.version import Version + + if verattr is None or Version(verattr) < Version(minversion): + raise Skipped( + f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}", + allow_module_level=True, + ) + return mod diff --git a/.venv/Lib/site-packages/_pytest/pastebin.py b/.venv/Lib/site-packages/_pytest/pastebin.py new file mode 100644 index 0000000..69c011e --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/pastebin.py @@ -0,0 +1,113 @@ +# mypy: allow-untyped-defs +"""Submit failure or test session information to a pastebin service.""" + +from __future__ import annotations + +from io import StringIO +import tempfile +from typing import IO + +from _pytest.config import Config +from _pytest.config import create_terminal_writer +from _pytest.config.argparsing import Parser +from _pytest.stash import StashKey +from _pytest.terminal import TerminalReporter +import pytest + + +pastebinfile_key = StashKey[IO[bytes]]() + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("terminal reporting") + group._addoption( + "--pastebin", + metavar="mode", + action="store", + dest="pastebin", + default=None, + choices=["failed", "all"], + help="Send failed|all info to bpaste.net pastebin service", + ) + + +@pytest.hookimpl(trylast=True) +def pytest_configure(config: Config) -> None: + if config.option.pastebin == "all": + tr = config.pluginmanager.getplugin("terminalreporter") + # If no terminal reporter plugin is present, nothing we can do here; + # this can happen when this function executes in a worker node + # when using pytest-xdist, for example. + if tr is not None: + # pastebin file will be UTF-8 encoded binary file. + config.stash[pastebinfile_key] = tempfile.TemporaryFile("w+b") + oldwrite = tr._tw.write + + def tee_write(s, **kwargs): + oldwrite(s, **kwargs) + if isinstance(s, str): + s = s.encode("utf-8") + config.stash[pastebinfile_key].write(s) + + tr._tw.write = tee_write + + +def pytest_unconfigure(config: Config) -> None: + if pastebinfile_key in config.stash: + pastebinfile = config.stash[pastebinfile_key] + # Get terminal contents and delete file. + pastebinfile.seek(0) + sessionlog = pastebinfile.read() + pastebinfile.close() + del config.stash[pastebinfile_key] + # Undo our patching in the terminal reporter. + tr = config.pluginmanager.getplugin("terminalreporter") + del tr._tw.__dict__["write"] + # Write summary. + tr.write_sep("=", "Sending information to Paste Service") + pastebinurl = create_new_paste(sessionlog) + tr.write_line(f"pastebin session-log: {pastebinurl}\n") + + +def create_new_paste(contents: str | bytes) -> str: + """Create a new paste using the bpaste.net service. + + :contents: Paste contents string. + :returns: URL to the pasted contents, or an error message. + """ + import re + from urllib.parse import urlencode + from urllib.request import urlopen + + params = {"code": contents, "lexer": "text", "expiry": "1week"} + url = "https://bpa.st" + try: + response: str = ( + urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8") + ) + except OSError as exc_info: # urllib errors + return f"bad response: {exc_info}" + m = re.search(r'href="/raw/(\w+)"', response) + if m: + return f"{url}/show/{m.group(1)}" + else: + return "bad response: invalid format ('" + response + "')" + + +def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: + if terminalreporter.config.option.pastebin != "failed": + return + if "failed" in terminalreporter.stats: + terminalreporter.write_sep("=", "Sending information to Paste Service") + for rep in terminalreporter.stats["failed"]: + try: + msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc + except AttributeError: + msg = terminalreporter._getfailureheadline(rep) + file = StringIO() + tw = create_terminal_writer(terminalreporter.config, file) + rep.toterminal(tw) + s = file.getvalue() + assert len(s) + pastebinurl = create_new_paste(s) + terminalreporter.write_line(f"{msg} --> {pastebinurl}") diff --git a/.venv/Lib/site-packages/_pytest/pathlib.py b/.venv/Lib/site-packages/_pytest/pathlib.py new file mode 100644 index 0000000..ca8a1d2 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/pathlib.py @@ -0,0 +1,1055 @@ +from __future__ import annotations + +import atexit +import contextlib +from enum import Enum +from errno import EBADF +from errno import ELOOP +from errno import ENOENT +from errno import ENOTDIR +import fnmatch +from functools import partial +from importlib.machinery import ModuleSpec +from importlib.machinery import PathFinder +import importlib.util +import itertools +import os +from os.path import expanduser +from os.path import expandvars +from os.path import isabs +from os.path import sep +from pathlib import Path +from pathlib import PurePath +from posixpath import sep as posix_sep +import shutil +import sys +import types +from types import ModuleType +from typing import Any +from typing import Callable +from typing import Iterable +from typing import Iterator +from typing import TypeVar +import uuid +import warnings + +from _pytest.compat import assert_never +from _pytest.outcomes import skip +from _pytest.warning_types import PytestWarning + + +if sys.version_info < (3, 11): + from importlib._bootstrap_external import _NamespaceLoader as NamespaceLoader +else: + from importlib.machinery import NamespaceLoader + +LOCK_TIMEOUT = 60 * 60 * 24 * 3 + +_AnyPurePath = TypeVar("_AnyPurePath", bound=PurePath) + +# The following function, variables and comments were +# copied from cpython 3.9 Lib/pathlib.py file. + +# EBADF - guard against macOS `stat` throwing EBADF +_IGNORED_ERRORS = (ENOENT, ENOTDIR, EBADF, ELOOP) + +_IGNORED_WINERRORS = ( + 21, # ERROR_NOT_READY - drive exists but is not accessible + 1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself +) + + +def _ignore_error(exception: Exception) -> bool: + return ( + getattr(exception, "errno", None) in _IGNORED_ERRORS + or getattr(exception, "winerror", None) in _IGNORED_WINERRORS + ) + + +def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: + return path.joinpath(".lock") + + +def on_rm_rf_error( + func: Callable[..., Any] | None, + path: str, + excinfo: BaseException + | tuple[type[BaseException], BaseException, types.TracebackType | None], + *, + start_path: Path, +) -> bool: + """Handle known read-only errors during rmtree. + + The returned value is used only by our own tests. + """ + if isinstance(excinfo, BaseException): + exc = excinfo + else: + exc = excinfo[1] + + # Another process removed the file in the middle of the "rm_rf" (xdist for example). + # More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018 + if isinstance(exc, FileNotFoundError): + return False + + if not isinstance(exc, PermissionError): + warnings.warn( + PytestWarning(f"(rm_rf) error removing {path}\n{type(exc)}: {exc}") + ) + return False + + if func not in (os.rmdir, os.remove, os.unlink): + if func not in (os.open,): + warnings.warn( + PytestWarning( + f"(rm_rf) unknown function {func} when removing {path}:\n{type(exc)}: {exc}" + ) + ) + return False + + # Chmod + retry. + import stat + + def chmod_rw(p: str) -> None: + mode = os.stat(p).st_mode + os.chmod(p, mode | stat.S_IRUSR | stat.S_IWUSR) + + # For files, we need to recursively go upwards in the directories to + # ensure they all are also writable. + p = Path(path) + if p.is_file(): + for parent in p.parents: + chmod_rw(str(parent)) + # Stop when we reach the original path passed to rm_rf. + if parent == start_path: + break + chmod_rw(str(path)) + + func(path) + return True + + +def ensure_extended_length_path(path: Path) -> Path: + """Get the extended-length version of a path (Windows). + + On Windows, by default, the maximum length of a path (MAX_PATH) is 260 + characters, and operations on paths longer than that fail. But it is possible + to overcome this by converting the path to "extended-length" form before + performing the operation: + https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation + + On Windows, this function returns the extended-length absolute version of path. + On other platforms it returns path unchanged. + """ + if sys.platform.startswith("win32"): + path = path.resolve() + path = Path(get_extended_length_path_str(str(path))) + return path + + +def get_extended_length_path_str(path: str) -> str: + """Convert a path to a Windows extended length path.""" + long_path_prefix = "\\\\?\\" + unc_long_path_prefix = "\\\\?\\UNC\\" + if path.startswith((long_path_prefix, unc_long_path_prefix)): + return path + # UNC + if path.startswith("\\\\"): + return unc_long_path_prefix + path[2:] + return long_path_prefix + path + + +def rm_rf(path: Path) -> None: + """Remove the path contents recursively, even if some elements + are read-only.""" + path = ensure_extended_length_path(path) + onerror = partial(on_rm_rf_error, start_path=path) + if sys.version_info >= (3, 12): + shutil.rmtree(str(path), onexc=onerror) + else: + shutil.rmtree(str(path), onerror=onerror) + + +def find_prefixed(root: Path, prefix: str) -> Iterator[os.DirEntry[str]]: + """Find all elements in root that begin with the prefix, case-insensitive.""" + l_prefix = prefix.lower() + for x in os.scandir(root): + if x.name.lower().startswith(l_prefix): + yield x + + +def extract_suffixes(iter: Iterable[os.DirEntry[str]], prefix: str) -> Iterator[str]: + """Return the parts of the paths following the prefix. + + :param iter: Iterator over path names. + :param prefix: Expected prefix of the path names. + """ + p_len = len(prefix) + for entry in iter: + yield entry.name[p_len:] + + +def find_suffixes(root: Path, prefix: str) -> Iterator[str]: + """Combine find_prefixes and extract_suffixes.""" + return extract_suffixes(find_prefixed(root, prefix), prefix) + + +def parse_num(maybe_num: str) -> int: + """Parse number path suffixes, returns -1 on error.""" + try: + return int(maybe_num) + except ValueError: + return -1 + + +def _force_symlink(root: Path, target: str | PurePath, link_to: str | Path) -> None: + """Helper to create the current symlink. + + It's full of race conditions that are reasonably OK to ignore + for the context of best effort linking to the latest test run. + + The presumption being that in case of much parallelism + the inaccuracy is going to be acceptable. + """ + current_symlink = root.joinpath(target) + try: + current_symlink.unlink() + except OSError: + pass + try: + current_symlink.symlink_to(link_to) + except Exception: + pass + + +def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path: + """Create a directory with an increased number as suffix for the given prefix.""" + for i in range(10): + # try up to 10 times to create the folder + max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) + new_number = max_existing + 1 + new_path = root.joinpath(f"{prefix}{new_number}") + try: + new_path.mkdir(mode=mode) + except Exception: + pass + else: + _force_symlink(root, prefix + "current", new_path) + return new_path + else: + raise OSError( + "could not create numbered dir with prefix " + f"{prefix} in {root} after 10 tries" + ) + + +def create_cleanup_lock(p: Path) -> Path: + """Create a lock to prevent premature folder cleanup.""" + lock_path = get_lock_path(p) + try: + fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644) + except FileExistsError as e: + raise OSError(f"cannot create lockfile in {p}") from e + else: + pid = os.getpid() + spid = str(pid).encode() + os.write(fd, spid) + os.close(fd) + if not lock_path.is_file(): + raise OSError("lock path got renamed after successful creation") + return lock_path + + +def register_cleanup_lock_removal( + lock_path: Path, register: Any = atexit.register +) -> Any: + """Register a cleanup function for removing a lock, by default on atexit.""" + pid = os.getpid() + + def cleanup_on_exit(lock_path: Path = lock_path, original_pid: int = pid) -> None: + current_pid = os.getpid() + if current_pid != original_pid: + # fork + return + try: + lock_path.unlink() + except OSError: + pass + + return register(cleanup_on_exit) + + +def maybe_delete_a_numbered_dir(path: Path) -> None: + """Remove a numbered directory if its lock can be obtained and it does + not seem to be in use.""" + path = ensure_extended_length_path(path) + lock_path = None + try: + lock_path = create_cleanup_lock(path) + parent = path.parent + + garbage = parent.joinpath(f"garbage-{uuid.uuid4()}") + path.rename(garbage) + rm_rf(garbage) + except OSError: + # known races: + # * other process did a cleanup at the same time + # * deletable folder was found + # * process cwd (Windows) + return + finally: + # If we created the lock, ensure we remove it even if we failed + # to properly remove the numbered dir. + if lock_path is not None: + try: + lock_path.unlink() + except OSError: + pass + + +def ensure_deletable(path: Path, consider_lock_dead_if_created_before: float) -> bool: + """Check if `path` is deletable based on whether the lock file is expired.""" + if path.is_symlink(): + return False + lock = get_lock_path(path) + try: + if not lock.is_file(): + return True + except OSError: + # we might not have access to the lock file at all, in this case assume + # we don't have access to the entire directory (#7491). + return False + try: + lock_time = lock.stat().st_mtime + except Exception: + return False + else: + if lock_time < consider_lock_dead_if_created_before: + # We want to ignore any errors while trying to remove the lock such as: + # - PermissionDenied, like the file permissions have changed since the lock creation; + # - FileNotFoundError, in case another pytest process got here first; + # and any other cause of failure. + with contextlib.suppress(OSError): + lock.unlink() + return True + return False + + +def try_cleanup(path: Path, consider_lock_dead_if_created_before: float) -> None: + """Try to cleanup a folder if we can ensure it's deletable.""" + if ensure_deletable(path, consider_lock_dead_if_created_before): + maybe_delete_a_numbered_dir(path) + + +def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]: + """List candidates for numbered directories to be removed - follows py.path.""" + max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) + max_delete = max_existing - keep + entries = find_prefixed(root, prefix) + entries, entries2 = itertools.tee(entries) + numbers = map(parse_num, extract_suffixes(entries2, prefix)) + for entry, number in zip(entries, numbers): + if number <= max_delete: + yield Path(entry) + + +def cleanup_dead_symlinks(root: Path) -> None: + for left_dir in root.iterdir(): + if left_dir.is_symlink(): + if not left_dir.resolve().exists(): + left_dir.unlink() + + +def cleanup_numbered_dir( + root: Path, prefix: str, keep: int, consider_lock_dead_if_created_before: float +) -> None: + """Cleanup for lock driven numbered directories.""" + if not root.exists(): + return + for path in cleanup_candidates(root, prefix, keep): + try_cleanup(path, consider_lock_dead_if_created_before) + for path in root.glob("garbage-*"): + try_cleanup(path, consider_lock_dead_if_created_before) + + cleanup_dead_symlinks(root) + + +def make_numbered_dir_with_cleanup( + root: Path, + prefix: str, + keep: int, + lock_timeout: float, + mode: int, +) -> Path: + """Create a numbered dir with a cleanup lock and remove old ones.""" + e = None + for i in range(10): + try: + p = make_numbered_dir(root, prefix, mode) + # Only lock the current dir when keep is not 0 + if keep != 0: + lock_path = create_cleanup_lock(p) + register_cleanup_lock_removal(lock_path) + except Exception as exc: + e = exc + else: + consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout + # Register a cleanup for program exit + atexit.register( + cleanup_numbered_dir, + root, + prefix, + keep, + consider_lock_dead_if_created_before, + ) + return p + assert e is not None + raise e + + +def resolve_from_str(input: str, rootpath: Path) -> Path: + input = expanduser(input) + input = expandvars(input) + if isabs(input): + return Path(input) + else: + return rootpath.joinpath(input) + + +def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool: + """A port of FNMatcher from py.path.common which works with PurePath() instances. + + The difference between this algorithm and PurePath.match() is that the + latter matches "**" glob expressions for each part of the path, while + this algorithm uses the whole path instead. + + For example: + "tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" + with this algorithm, but not with PurePath.match(). + + This algorithm was ported to keep backward-compatibility with existing + settings which assume paths match according this logic. + + References: + * https://bugs.python.org/issue29249 + * https://bugs.python.org/issue34731 + """ + path = PurePath(path) + iswin32 = sys.platform.startswith("win") + + if iswin32 and sep not in pattern and posix_sep in pattern: + # Running on Windows, the pattern has no Windows path separators, + # and the pattern has one or more Posix path separators. Replace + # the Posix path separators with the Windows path separator. + pattern = pattern.replace(posix_sep, sep) + + if sep not in pattern: + name = path.name + else: + name = str(path) + if path.is_absolute() and not os.path.isabs(pattern): + pattern = f"*{os.sep}{pattern}" + return fnmatch.fnmatch(name, pattern) + + +def parts(s: str) -> set[str]: + parts = s.split(sep) + return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} + + +def symlink_or_skip( + src: os.PathLike[str] | str, + dst: os.PathLike[str] | str, + **kwargs: Any, +) -> None: + """Make a symlink, or skip the test in case symlinks are not supported.""" + try: + os.symlink(src, dst, **kwargs) + except OSError as e: + skip(f"symlinks not supported: {e}") + + +class ImportMode(Enum): + """Possible values for `mode` parameter of `import_path`.""" + + prepend = "prepend" + append = "append" + importlib = "importlib" + + +class ImportPathMismatchError(ImportError): + """Raised on import_path() if there is a mismatch of __file__'s. + + This can happen when `import_path` is called multiple times with different filenames that has + the same basename but reside in packages + (for example "/tests1/test_foo.py" and "/tests2/test_foo.py"). + """ + + +def import_path( + path: str | os.PathLike[str], + *, + mode: str | ImportMode = ImportMode.prepend, + root: Path, + consider_namespace_packages: bool, +) -> ModuleType: + """ + Import and return a module from the given path, which can be a file (a module) or + a directory (a package). + + :param path: + Path to the file to import. + + :param mode: + Controls the underlying import mechanism that will be used: + + * ImportMode.prepend: the directory containing the module (or package, taking + `__init__.py` files into account) will be put at the *start* of `sys.path` before + being imported with `importlib.import_module`. + + * ImportMode.append: same as `prepend`, but the directory will be appended + to the end of `sys.path`, if not already in `sys.path`. + + * ImportMode.importlib: uses more fine control mechanisms provided by `importlib` + to import the module, which avoids having to muck with `sys.path` at all. It effectively + allows having same-named test modules in different places. + + :param root: + Used as an anchor when mode == ImportMode.importlib to obtain + a unique name for the module being imported so it can safely be stored + into ``sys.modules``. + + :param consider_namespace_packages: + If True, consider namespace packages when resolving module names. + + :raises ImportPathMismatchError: + If after importing the given `path` and the module `__file__` + are different. Only raised in `prepend` and `append` modes. + """ + path = Path(path) + mode = ImportMode(mode) + + if not path.exists(): + raise ImportError(path) + + if mode is ImportMode.importlib: + # Try to import this module using the standard import mechanisms, but + # without touching sys.path. + try: + pkg_root, module_name = resolve_pkg_root_and_module_name( + path, consider_namespace_packages=consider_namespace_packages + ) + except CouldNotResolvePathError: + pass + else: + # If the given module name is already in sys.modules, do not import it again. + with contextlib.suppress(KeyError): + return sys.modules[module_name] + + mod = _import_module_using_spec( + module_name, path, pkg_root, insert_modules=False + ) + if mod is not None: + return mod + + # Could not import the module with the current sys.path, so we fall back + # to importing the file as a single module, not being a part of a package. + module_name = module_name_from_path(path, root) + with contextlib.suppress(KeyError): + return sys.modules[module_name] + + mod = _import_module_using_spec( + module_name, path, path.parent, insert_modules=True + ) + if mod is None: + raise ImportError(f"Can't find module {module_name} at location {path}") + return mod + + try: + pkg_root, module_name = resolve_pkg_root_and_module_name( + path, consider_namespace_packages=consider_namespace_packages + ) + except CouldNotResolvePathError: + pkg_root, module_name = path.parent, path.stem + + # Change sys.path permanently: restoring it at the end of this function would cause surprising + # problems because of delayed imports: for example, a conftest.py file imported by this function + # might have local imports, which would fail at runtime if we restored sys.path. + if mode is ImportMode.append: + if str(pkg_root) not in sys.path: + sys.path.append(str(pkg_root)) + elif mode is ImportMode.prepend: + if str(pkg_root) != sys.path[0]: + sys.path.insert(0, str(pkg_root)) + else: + assert_never(mode) + + importlib.import_module(module_name) + + mod = sys.modules[module_name] + if path.name == "__init__.py": + return mod + + ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "") + if ignore != "1": + module_file = mod.__file__ + if module_file is None: + raise ImportPathMismatchError(module_name, module_file, path) + + if module_file.endswith((".pyc", ".pyo")): + module_file = module_file[:-1] + if module_file.endswith(os.sep + "__init__.py"): + module_file = module_file[: -(len(os.sep + "__init__.py"))] + + try: + is_same = _is_same(str(path), module_file) + except FileNotFoundError: + is_same = False + + if not is_same: + raise ImportPathMismatchError(module_name, module_file, path) + + return mod + + +def _import_module_using_spec( + module_name: str, module_path: Path, module_location: Path, *, insert_modules: bool +) -> ModuleType | None: + """ + Tries to import a module by its canonical name, path, and its parent location. + + :param module_name: + The expected module name, will become the key of `sys.modules`. + + :param module_path: + The file path of the module, for example `/foo/bar/test_demo.py`. + If module is a package, pass the path to the `__init__.py` of the package. + If module is a namespace package, pass directory path. + + :param module_location: + The parent location of the module. + If module is a package, pass the directory containing the `__init__.py` file. + + :param insert_modules: + If True, will call `insert_missing_modules` to create empty intermediate modules + with made-up module names (when importing test files not reachable from `sys.path`). + + Example 1 of parent_module_*: + + module_name: "a.b.c.demo" + module_path: Path("a/b/c/demo.py") + module_location: Path("a/b/c/") + if "a.b.c" is package ("a/b/c/__init__.py" exists), then + parent_module_name: "a.b.c" + parent_module_path: Path("a/b/c/__init__.py") + parent_module_location: Path("a/b/c/") + else: + parent_module_name: "a.b.c" + parent_module_path: Path("a/b/c") + parent_module_location: Path("a/b/") + + Example 2 of parent_module_*: + + module_name: "a.b.c" + module_path: Path("a/b/c/__init__.py") + module_location: Path("a/b/c/") + if "a.b" is package ("a/b/__init__.py" exists), then + parent_module_name: "a.b" + parent_module_path: Path("a/b/__init__.py") + parent_module_location: Path("a/b/") + else: + parent_module_name: "a.b" + parent_module_path: Path("a/b/") + parent_module_location: Path("a/") + """ + # Attempt to import the parent module, seems is our responsibility: + # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311 + parent_module_name, _, name = module_name.rpartition(".") + parent_module: ModuleType | None = None + if parent_module_name: + parent_module = sys.modules.get(parent_module_name) + # If the parent_module lacks the `__path__` attribute, AttributeError when finding a submodule's spec, + # requiring re-import according to the path. + need_reimport = not hasattr(parent_module, "__path__") + if parent_module is None or need_reimport: + # Get parent_location based on location, get parent_path based on path. + if module_path.name == "__init__.py": + # If the current module is in a package, + # need to leave the package first and then enter the parent module. + parent_module_path = module_path.parent.parent + else: + parent_module_path = module_path.parent + + if (parent_module_path / "__init__.py").is_file(): + # If the parent module is a package, loading by __init__.py file. + parent_module_path = parent_module_path / "__init__.py" + + parent_module = _import_module_using_spec( + parent_module_name, + parent_module_path, + parent_module_path.parent, + insert_modules=insert_modules, + ) + + # Checking with sys.meta_path first in case one of its hooks can import this module, + # such as our own assertion-rewrite hook. + for meta_importer in sys.meta_path: + module_name_of_meta = getattr(meta_importer.__class__, "__module__", "") + if module_name_of_meta == "_pytest.assertion.rewrite" and module_path.is_file(): + # Import modules in subdirectories by module_path + # to ensure assertion rewrites are not missed (#12659). + find_spec_path = [str(module_location), str(module_path)] + else: + find_spec_path = [str(module_location)] + + spec = meta_importer.find_spec(module_name, find_spec_path) + + if spec_matches_module_path(spec, module_path): + break + else: + loader = None + if module_path.is_dir(): + # The `spec_from_file_location` matches a loader based on the file extension by default. + # For a namespace package, need to manually specify a loader. + loader = NamespaceLoader(name, module_path, PathFinder()) + + spec = importlib.util.spec_from_file_location( + module_name, str(module_path), loader=loader + ) + + if spec_matches_module_path(spec, module_path): + assert spec is not None + # Find spec and import this module. + mod = importlib.util.module_from_spec(spec) + sys.modules[module_name] = mod + spec.loader.exec_module(mod) # type: ignore[union-attr] + + # Set this module as an attribute of the parent module (#12194). + if parent_module is not None: + setattr(parent_module, name, mod) + + if insert_modules: + insert_missing_modules(sys.modules, module_name) + return mod + + return None + + +def spec_matches_module_path(module_spec: ModuleSpec | None, module_path: Path) -> bool: + """Return true if the given ModuleSpec can be used to import the given module path.""" + if module_spec is None: + return False + + if module_spec.origin: + return Path(module_spec.origin) == module_path + + # Compare the path with the `module_spec.submodule_Search_Locations` in case + # the module is part of a namespace package. + # https://docs.python.org/3/library/importlib.html#importlib.machinery.ModuleSpec.submodule_search_locations + if module_spec.submodule_search_locations: # can be None. + for path in module_spec.submodule_search_locations: + if Path(path) == module_path: + return True + + return False + + +# Implement a special _is_same function on Windows which returns True if the two filenames +# compare equal, to circumvent os.path.samefile returning False for mounts in UNC (#7678). +if sys.platform.startswith("win"): + + def _is_same(f1: str, f2: str) -> bool: + return Path(f1) == Path(f2) or os.path.samefile(f1, f2) + +else: + + def _is_same(f1: str, f2: str) -> bool: + return os.path.samefile(f1, f2) + + +def module_name_from_path(path: Path, root: Path) -> str: + """ + Return a dotted module name based on the given path, anchored on root. + + For example: path="projects/src/tests/test_foo.py" and root="/projects", the + resulting module name will be "src.tests.test_foo". + """ + path = path.with_suffix("") + try: + relative_path = path.relative_to(root) + except ValueError: + # If we can't get a relative path to root, use the full path, except + # for the first part ("d:\\" or "/" depending on the platform, for example). + path_parts = path.parts[1:] + else: + # Use the parts for the relative path to the root path. + path_parts = relative_path.parts + + # Module name for packages do not contain the __init__ file, unless + # the `__init__.py` file is at the root. + if len(path_parts) >= 2 and path_parts[-1] == "__init__": + path_parts = path_parts[:-1] + + # Module names cannot contain ".", normalize them to "_". This prevents + # a directory having a "." in the name (".env.310" for example) causing extra intermediate modules. + # Also, important to replace "." at the start of paths, as those are considered relative imports. + path_parts = tuple(x.replace(".", "_") for x in path_parts) + + return ".".join(path_parts) + + +def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None: + """ + Used by ``import_path`` to create intermediate modules when using mode=importlib. + + When we want to import a module as "src.tests.test_foo" for example, we need + to create empty modules "src" and "src.tests" after inserting "src.tests.test_foo", + otherwise "src.tests.test_foo" is not importable by ``__import__``. + """ + module_parts = module_name.split(".") + while module_name: + parent_module_name, _, child_name = module_name.rpartition(".") + if parent_module_name: + parent_module = modules.get(parent_module_name) + if parent_module is None: + try: + # If sys.meta_path is empty, calling import_module will issue + # a warning and raise ModuleNotFoundError. To avoid the + # warning, we check sys.meta_path explicitly and raise the error + # ourselves to fall back to creating a dummy module. + if not sys.meta_path: + raise ModuleNotFoundError + parent_module = importlib.import_module(parent_module_name) + except ModuleNotFoundError: + parent_module = ModuleType( + module_name, + doc="Empty module created by pytest's importmode=importlib.", + ) + modules[parent_module_name] = parent_module + + # Add child attribute to the parent that can reference the child + # modules. + if not hasattr(parent_module, child_name): + setattr(parent_module, child_name, modules[module_name]) + + module_parts.pop(-1) + module_name = ".".join(module_parts) + + +def resolve_package_path(path: Path) -> Path | None: + """Return the Python package path by looking for the last + directory upwards which still contains an __init__.py. + + Returns None if it cannot be determined. + """ + result = None + for parent in itertools.chain((path,), path.parents): + if parent.is_dir(): + if not (parent / "__init__.py").is_file(): + break + if not parent.name.isidentifier(): + break + result = parent + return result + + +def resolve_pkg_root_and_module_name( + path: Path, *, consider_namespace_packages: bool = False +) -> tuple[Path, str]: + """ + Return the path to the directory of the root package that contains the + given Python file, and its module name: + + src/ + app/ + __init__.py + core/ + __init__.py + models.py + + Passing the full path to `models.py` will yield Path("src") and "app.core.models". + + If consider_namespace_packages is True, then we additionally check upwards in the hierarchy + for namespace packages: + + https://packaging.python.org/en/latest/guides/packaging-namespace-packages + + Raises CouldNotResolvePathError if the given path does not belong to a package (missing any __init__.py files). + """ + pkg_root: Path | None = None + pkg_path = resolve_package_path(path) + if pkg_path is not None: + pkg_root = pkg_path.parent + if consider_namespace_packages: + start = pkg_root if pkg_root is not None else path.parent + for candidate in (start, *start.parents): + module_name = compute_module_name(candidate, path) + if module_name and is_importable(module_name, path): + # Point the pkg_root to the root of the namespace package. + pkg_root = candidate + break + + if pkg_root is not None: + module_name = compute_module_name(pkg_root, path) + if module_name: + return pkg_root, module_name + + raise CouldNotResolvePathError(f"Could not resolve for {path}") + + +def is_importable(module_name: str, module_path: Path) -> bool: + """ + Return if the given module path could be imported normally by Python, akin to the user + entering the REPL and importing the corresponding module name directly, and corresponds + to the module_path specified. + + :param module_name: + Full module name that we want to check if is importable. + For example, "app.models". + + :param module_path: + Full path to the python module/package we want to check if is importable. + For example, "/projects/src/app/models.py". + """ + try: + # Note this is different from what we do in ``_import_module_using_spec``, where we explicitly search through + # sys.meta_path to be able to pass the path of the module that we want to import (``meta_importer.find_spec``). + # Using importlib.util.find_spec() is different, it gives the same results as trying to import + # the module normally in the REPL. + spec = importlib.util.find_spec(module_name) + except (ImportError, ValueError, ImportWarning): + return False + else: + return spec_matches_module_path(spec, module_path) + + +def compute_module_name(root: Path, module_path: Path) -> str | None: + """Compute a module name based on a path and a root anchor.""" + try: + path_without_suffix = module_path.with_suffix("") + except ValueError: + # Empty paths (such as Path.cwd()) might break meta_path hooks (like our own assertion rewriter). + return None + + try: + relative = path_without_suffix.relative_to(root) + except ValueError: # pragma: no cover + return None + names = list(relative.parts) + if not names: + return None + if names[-1] == "__init__": + names.pop() + return ".".join(names) + + +class CouldNotResolvePathError(Exception): + """Custom exception raised by resolve_pkg_root_and_module_name.""" + + +def scandir( + path: str | os.PathLike[str], + sort_key: Callable[[os.DirEntry[str]], object] = lambda entry: entry.name, +) -> list[os.DirEntry[str]]: + """Scan a directory recursively, in breadth-first order. + + The returned entries are sorted according to the given key. + The default is to sort by name. + If the directory does not exist, return an empty list. + """ + entries = [] + # Attempt to create a scandir iterator for the given path. + try: + scandir_iter = os.scandir(path) + except FileNotFoundError: + # If the directory does not exist, return an empty list. + return [] + # Use the scandir iterator in a context manager to ensure it is properly closed. + with scandir_iter as s: + for entry in s: + try: + entry.is_file() + except OSError as err: + if _ignore_error(err): + continue + # Reraise non-ignorable errors to avoid hiding issues. + raise + entries.append(entry) + entries.sort(key=sort_key) # type: ignore[arg-type] + return entries + + +def visit( + path: str | os.PathLike[str], recurse: Callable[[os.DirEntry[str]], bool] +) -> Iterator[os.DirEntry[str]]: + """Walk a directory recursively, in breadth-first order. + + The `recurse` predicate determines whether a directory is recursed. + + Entries at each directory level are sorted. + """ + entries = scandir(path) + yield from entries + for entry in entries: + if entry.is_dir() and recurse(entry): + yield from visit(entry.path, recurse) + + +def absolutepath(path: str | os.PathLike[str]) -> Path: + """Convert a path to an absolute path using os.path.abspath. + + Prefer this over Path.resolve() (see #6523). + Prefer this over Path.absolute() (not public, doesn't normalize). + """ + return Path(os.path.abspath(path)) + + +def commonpath(path1: Path, path2: Path) -> Path | None: + """Return the common part shared with the other path, or None if there is + no common part. + + If one path is relative and one is absolute, returns None. + """ + try: + return Path(os.path.commonpath((str(path1), str(path2)))) + except ValueError: + return None + + +def bestrelpath(directory: Path, dest: Path) -> str: + """Return a string which is a relative path from directory to dest such + that directory/bestrelpath == dest. + + The paths must be either both absolute or both relative. + + If no such path can be determined, returns dest. + """ + assert isinstance(directory, Path) + assert isinstance(dest, Path) + if dest == directory: + return os.curdir + # Find the longest common directory. + base = commonpath(directory, dest) + # Can be the case on Windows for two absolute paths on different drives. + # Can be the case for two relative paths without common prefix. + # Can be the case for a relative path and an absolute path. + if not base: + return str(dest) + reldirectory = directory.relative_to(base) + reldest = dest.relative_to(base) + return os.path.join( + # Back from directory to base. + *([os.pardir] * len(reldirectory.parts)), + # Forward from base to dest. + *reldest.parts, + ) + + +def safe_exists(p: Path) -> bool: + """Like Path.exists(), but account for input arguments that might be too long (#11394).""" + try: + return p.exists() + except (ValueError, OSError): + # ValueError: stat: path too long for Windows + # OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect + return False diff --git a/.venv/Lib/site-packages/_pytest/py.typed b/.venv/Lib/site-packages/_pytest/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/_pytest/pytester.py b/.venv/Lib/site-packages/_pytest/pytester.py new file mode 100644 index 0000000..3f7520e --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/pytester.py @@ -0,0 +1,1766 @@ +# mypy: allow-untyped-defs +"""(Disabled by default) support for testing pytest and pytest plugins. + +PYTEST_DONT_REWRITE +""" + +from __future__ import annotations + +import collections.abc +import contextlib +from fnmatch import fnmatch +import gc +import importlib +from io import StringIO +import locale +import os +from pathlib import Path +import platform +import re +import shutil +import subprocess +import sys +import traceback +from typing import Any +from typing import Callable +from typing import Final +from typing import final +from typing import Generator +from typing import IO +from typing import Iterable +from typing import Literal +from typing import overload +from typing import Sequence +from typing import TextIO +from typing import TYPE_CHECKING +from weakref import WeakKeyDictionary + +from iniconfig import IniConfig +from iniconfig import SectionWrapper + +from _pytest import timing +from _pytest._code import Source +from _pytest.capture import _get_multicapture +from _pytest.compat import NOTSET +from _pytest.compat import NotSetType +from _pytest.config import _PluggyPlugin +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config import main +from _pytest.config import PytestPluginManager +from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest +from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureRequest +from _pytest.main import Session +from _pytest.monkeypatch import MonkeyPatch +from _pytest.nodes import Collector +from _pytest.nodes import Item +from _pytest.outcomes import fail +from _pytest.outcomes import importorskip +from _pytest.outcomes import skip +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import make_numbered_dir +from _pytest.reports import CollectReport +from _pytest.reports import TestReport +from _pytest.tmpdir import TempPathFactory +from _pytest.warning_types import PytestWarning + + +if TYPE_CHECKING: + import pexpect + + +pytest_plugins = ["pytester_assertions"] + + +IGNORE_PAM = [ # filenames added when obtaining details about the current user + "/var/lib/sss/mc/passwd" +] + + +def pytest_addoption(parser: Parser) -> None: + parser.addoption( + "--lsof", + action="store_true", + dest="lsof", + default=False, + help="Run FD checks if lsof is available", + ) + + parser.addoption( + "--runpytest", + default="inprocess", + dest="runpytest", + choices=("inprocess", "subprocess"), + help=( + "Run pytest sub runs in tests using an 'inprocess' " + "or 'subprocess' (python -m main) method" + ), + ) + + parser.addini( + "pytester_example_dir", help="Directory to take the pytester example files from" + ) + + +def pytest_configure(config: Config) -> None: + if config.getvalue("lsof"): + checker = LsofFdLeakChecker() + if checker.matching_platform(): + config.pluginmanager.register(checker) + + config.addinivalue_line( + "markers", + "pytester_example_path(*path_segments): join the given path " + "segments to `pytester_example_dir` for this test.", + ) + + +class LsofFdLeakChecker: + def get_open_files(self) -> list[tuple[str, str]]: + if sys.version_info >= (3, 11): + # New in Python 3.11, ignores utf-8 mode + encoding = locale.getencoding() + else: + encoding = locale.getpreferredencoding(False) + out = subprocess.run( + ("lsof", "-Ffn0", "-p", str(os.getpid())), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + check=True, + text=True, + encoding=encoding, + ).stdout + + def isopen(line: str) -> bool: + return line.startswith("f") and ( + "deleted" not in line + and "mem" not in line + and "txt" not in line + and "cwd" not in line + ) + + open_files = [] + + for line in out.split("\n"): + if isopen(line): + fields = line.split("\0") + fd = fields[0][1:] + filename = fields[1][1:] + if filename in IGNORE_PAM: + continue + if filename.startswith("/"): + open_files.append((fd, filename)) + + return open_files + + def matching_platform(self) -> bool: + try: + subprocess.run(("lsof", "-v"), check=True) + except (OSError, subprocess.CalledProcessError): + return False + else: + return True + + @hookimpl(wrapper=True, tryfirst=True) + def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object]: + lines1 = self.get_open_files() + try: + return (yield) + finally: + if hasattr(sys, "pypy_version_info"): + gc.collect() + lines2 = self.get_open_files() + + new_fds = {t[0] for t in lines2} - {t[0] for t in lines1} + leaked_files = [t for t in lines2 if t[0] in new_fds] + if leaked_files: + error = [ + f"***** {len(leaked_files)} FD leakage detected", + *(str(f) for f in leaked_files), + "*** Before:", + *(str(f) for f in lines1), + "*** After:", + *(str(f) for f in lines2), + f"***** {len(leaked_files)} FD leakage detected", + "*** function {}:{}: {} ".format(*item.location), + "See issue #2366", + ] + item.warn(PytestWarning("\n".join(error))) + + +# used at least by pytest-xdist plugin + + +@fixture +def _pytest(request: FixtureRequest) -> PytestArg: + """Return a helper which offers a gethookrecorder(hook) method which + returns a HookRecorder instance which helps to make assertions about called + hooks.""" + return PytestArg(request) + + +class PytestArg: + def __init__(self, request: FixtureRequest) -> None: + self._request = request + + def gethookrecorder(self, hook) -> HookRecorder: + hookrecorder = HookRecorder(hook._pm) + self._request.addfinalizer(hookrecorder.finish_recording) + return hookrecorder + + +def get_public_names(values: Iterable[str]) -> list[str]: + """Only return names from iterator values without a leading underscore.""" + return [x for x in values if x[0] != "_"] + + +@final +class RecordedHookCall: + """A recorded call to a hook. + + The arguments to the hook call are set as attributes. + For example: + + .. code-block:: python + + calls = hook_recorder.getcalls("pytest_runtest_setup") + # Suppose pytest_runtest_setup was called once with `item=an_item`. + assert calls[0].item is an_item + """ + + def __init__(self, name: str, kwargs) -> None: + self.__dict__.update(kwargs) + self._name = name + + def __repr__(self) -> str: + d = self.__dict__.copy() + del d["_name"] + return f"" + + if TYPE_CHECKING: + # The class has undetermined attributes, this tells mypy about it. + def __getattr__(self, key: str): ... + + +@final +class HookRecorder: + """Record all hooks called in a plugin manager. + + Hook recorders are created by :class:`Pytester`. + + This wraps all the hook calls in the plugin manager, recording each call + before propagating the normal calls. + """ + + def __init__( + self, pluginmanager: PytestPluginManager, *, _ispytest: bool = False + ) -> None: + check_ispytest(_ispytest) + + self._pluginmanager = pluginmanager + self.calls: list[RecordedHookCall] = [] + self.ret: int | ExitCode | None = None + + def before(hook_name: str, hook_impls, kwargs) -> None: + self.calls.append(RecordedHookCall(hook_name, kwargs)) + + def after(outcome, hook_name: str, hook_impls, kwargs) -> None: + pass + + self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after) + + def finish_recording(self) -> None: + self._undo_wrapping() + + def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]: + """Get all recorded calls to hooks with the given names (or name).""" + if isinstance(names, str): + names = names.split() + return [call for call in self.calls if call._name in names] + + def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None: + __tracebackhide__ = True + i = 0 + entries = list(entries) + # Since Python 3.13, f_locals is not a dict, but eval requires a dict. + backlocals = dict(sys._getframe(1).f_locals) + while entries: + name, check = entries.pop(0) + for ind, call in enumerate(self.calls[i:]): + if call._name == name: + print("NAMEMATCH", name, call) + if eval(check, backlocals, call.__dict__): + print("CHECKERMATCH", repr(check), "->", call) + else: + print("NOCHECKERMATCH", repr(check), "-", call) + continue + i += ind + 1 + break + print("NONAMEMATCH", name, "with", call) + else: + fail(f"could not find {name!r} check {check!r}") + + def popcall(self, name: str) -> RecordedHookCall: + __tracebackhide__ = True + for i, call in enumerate(self.calls): + if call._name == name: + del self.calls[i] + return call + lines = [f"could not find call {name!r}, in:"] + lines.extend([f" {x}" for x in self.calls]) + fail("\n".join(lines)) + + def getcall(self, name: str) -> RecordedHookCall: + values = self.getcalls(name) + assert len(values) == 1, (name, values) + return values[0] + + # functionality for test reports + + @overload + def getreports( + self, + names: Literal["pytest_collectreport"], + ) -> Sequence[CollectReport]: ... + + @overload + def getreports( + self, + names: Literal["pytest_runtest_logreport"], + ) -> Sequence[TestReport]: ... + + @overload + def getreports( + self, + names: str | Iterable[str] = ( + "pytest_collectreport", + "pytest_runtest_logreport", + ), + ) -> Sequence[CollectReport | TestReport]: ... + + def getreports( + self, + names: str | Iterable[str] = ( + "pytest_collectreport", + "pytest_runtest_logreport", + ), + ) -> Sequence[CollectReport | TestReport]: + return [x.report for x in self.getcalls(names)] + + def matchreport( + self, + inamepart: str = "", + names: str | Iterable[str] = ( + "pytest_runtest_logreport", + "pytest_collectreport", + ), + when: str | None = None, + ) -> CollectReport | TestReport: + """Return a testreport whose dotted import path matches.""" + values = [] + for rep in self.getreports(names=names): + if not when and rep.when != "call" and rep.passed: + # setup/teardown passing reports - let's ignore those + continue + if when and rep.when != when: + continue + if not inamepart or inamepart in rep.nodeid.split("::"): + values.append(rep) + if not values: + raise ValueError( + f"could not find test report matching {inamepart!r}: " + "no test reports at all!" + ) + if len(values) > 1: + raise ValueError( + f"found 2 or more testreports matching {inamepart!r}: {values}" + ) + return values[0] + + @overload + def getfailures( + self, + names: Literal["pytest_collectreport"], + ) -> Sequence[CollectReport]: ... + + @overload + def getfailures( + self, + names: Literal["pytest_runtest_logreport"], + ) -> Sequence[TestReport]: ... + + @overload + def getfailures( + self, + names: str | Iterable[str] = ( + "pytest_collectreport", + "pytest_runtest_logreport", + ), + ) -> Sequence[CollectReport | TestReport]: ... + + def getfailures( + self, + names: str | Iterable[str] = ( + "pytest_collectreport", + "pytest_runtest_logreport", + ), + ) -> Sequence[CollectReport | TestReport]: + return [rep for rep in self.getreports(names) if rep.failed] + + def getfailedcollections(self) -> Sequence[CollectReport]: + return self.getfailures("pytest_collectreport") + + def listoutcomes( + self, + ) -> tuple[ + Sequence[TestReport], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], + ]: + passed = [] + skipped = [] + failed = [] + for rep in self.getreports( + ("pytest_collectreport", "pytest_runtest_logreport") + ): + if rep.passed: + if rep.when == "call": + assert isinstance(rep, TestReport) + passed.append(rep) + elif rep.skipped: + skipped.append(rep) + else: + assert rep.failed, f"Unexpected outcome: {rep!r}" + failed.append(rep) + return passed, skipped, failed + + def countoutcomes(self) -> list[int]: + return [len(x) for x in self.listoutcomes()] + + def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None: + __tracebackhide__ = True + from _pytest.pytester_assertions import assertoutcome + + outcomes = self.listoutcomes() + assertoutcome( + outcomes, + passed=passed, + skipped=skipped, + failed=failed, + ) + + def clear(self) -> None: + self.calls[:] = [] + + +@fixture +def linecomp() -> LineComp: + """A :class: `LineComp` instance for checking that an input linearly + contains a sequence of strings.""" + return LineComp() + + +@fixture(name="LineMatcher") +def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]: + """A reference to the :class: `LineMatcher`. + + This is instantiable with a list of lines (without their trailing newlines). + This is useful for testing large texts, such as the output of commands. + """ + return LineMatcher + + +@fixture +def pytester( + request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch +) -> Pytester: + """ + Facilities to write tests/configuration files, execute pytest in isolation, and match + against expected output, perfect for black-box testing of pytest plugins. + + It attempts to isolate the test run from external factors as much as possible, modifying + the current working directory to ``path`` and environment variables during initialization. + + It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path` + fixture but provides methods which aid in testing pytest itself. + """ + return Pytester(request, tmp_path_factory, monkeypatch, _ispytest=True) + + +@fixture +def _sys_snapshot() -> Generator[None]: + snappaths = SysPathsSnapshot() + snapmods = SysModulesSnapshot() + yield + snapmods.restore() + snappaths.restore() + + +@fixture +def _config_for_test() -> Generator[Config]: + from _pytest.config import get_config + + config = get_config() + yield config + config._ensure_unconfigure() # cleanup, e.g. capman closing tmpfiles. + + +# Regex to match the session duration string in the summary: "74.34s". +rex_session_duration = re.compile(r"\d+\.\d\ds") +# Regex to match all the counts and phrases in the summary line: "34 passed, 111 skipped". +rex_outcome = re.compile(r"(\d+) (\w+)") + + +@final +class RunResult: + """The result of running a command from :class:`~pytest.Pytester`.""" + + def __init__( + self, + ret: int | ExitCode, + outlines: list[str], + errlines: list[str], + duration: float, + ) -> None: + try: + self.ret: int | ExitCode = ExitCode(ret) + """The return value.""" + except ValueError: + self.ret = ret + self.outlines = outlines + """List of lines captured from stdout.""" + self.errlines = errlines + """List of lines captured from stderr.""" + self.stdout = LineMatcher(outlines) + """:class:`~pytest.LineMatcher` of stdout. + + Use e.g. :func:`str(stdout) ` to reconstruct stdout, or the commonly used + :func:`stdout.fnmatch_lines() ` method. + """ + self.stderr = LineMatcher(errlines) + """:class:`~pytest.LineMatcher` of stderr.""" + self.duration = duration + """Duration in seconds.""" + + def __repr__(self) -> str: + return ( + "" + % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) + ) + + def parseoutcomes(self) -> dict[str, int]: + """Return a dictionary of outcome noun -> count from parsing the terminal + output that the test process produced. + + The returned nouns will always be in plural form:: + + ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== + + Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. + """ + return self.parse_summary_nouns(self.outlines) + + @classmethod + def parse_summary_nouns(cls, lines) -> dict[str, int]: + """Extract the nouns from a pytest terminal summary line. + + It always returns the plural noun for consistency:: + + ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== + + Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. + """ + for line in reversed(lines): + if rex_session_duration.search(line): + outcomes = rex_outcome.findall(line) + ret = {noun: int(count) for (count, noun) in outcomes} + break + else: + raise ValueError("Pytest terminal summary report not found") + + to_plural = { + "warning": "warnings", + "error": "errors", + } + return {to_plural.get(k, k): v for k, v in ret.items()} + + def assert_outcomes( + self, + passed: int = 0, + skipped: int = 0, + failed: int = 0, + errors: int = 0, + xpassed: int = 0, + xfailed: int = 0, + warnings: int | None = None, + deselected: int | None = None, + ) -> None: + """ + Assert that the specified outcomes appear with the respective + numbers (0 means it didn't occur) in the text output from a test run. + + ``warnings`` and ``deselected`` are only checked if not None. + """ + __tracebackhide__ = True + from _pytest.pytester_assertions import assert_outcomes + + outcomes = self.parseoutcomes() + assert_outcomes( + outcomes, + passed=passed, + skipped=skipped, + failed=failed, + errors=errors, + xpassed=xpassed, + xfailed=xfailed, + warnings=warnings, + deselected=deselected, + ) + + +class SysModulesSnapshot: + def __init__(self, preserve: Callable[[str], bool] | None = None) -> None: + self.__preserve = preserve + self.__saved = dict(sys.modules) + + def restore(self) -> None: + if self.__preserve: + self.__saved.update( + (k, m) for k, m in sys.modules.items() if self.__preserve(k) + ) + sys.modules.clear() + sys.modules.update(self.__saved) + + +class SysPathsSnapshot: + def __init__(self) -> None: + self.__saved = list(sys.path), list(sys.meta_path) + + def restore(self) -> None: + sys.path[:], sys.meta_path[:] = self.__saved + + +@final +class Pytester: + """ + Facilities to write tests/configuration files, execute pytest in isolation, and match + against expected output, perfect for black-box testing of pytest plugins. + + It attempts to isolate the test run from external factors as much as possible, modifying + the current working directory to :attr:`path` and environment variables during initialization. + """ + + __test__ = False + + CLOSE_STDIN: Final = NOTSET + + class TimeoutExpired(Exception): + pass + + def __init__( + self, + request: FixtureRequest, + tmp_path_factory: TempPathFactory, + monkeypatch: MonkeyPatch, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self._request = request + self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = ( + WeakKeyDictionary() + ) + if request.function: + name: str = request.function.__name__ + else: + name = request.node.name + self._name = name + self._path: Path = tmp_path_factory.mktemp(name, numbered=True) + #: A list of plugins to use with :py:meth:`parseconfig` and + #: :py:meth:`runpytest`. Initially this is an empty list but plugins can + #: be added to the list. The type of items to add to the list depends on + #: the method using them so refer to them for details. + self.plugins: list[str | _PluggyPlugin] = [] + self._sys_path_snapshot = SysPathsSnapshot() + self._sys_modules_snapshot = self.__take_sys_modules_snapshot() + self._request.addfinalizer(self._finalize) + self._method = self._request.config.getoption("--runpytest") + self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True) + + self._monkeypatch = mp = monkeypatch + self.chdir() + mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot)) + # Ensure no unexpected caching via tox. + mp.delenv("TOX_ENV_DIR", raising=False) + # Discard outer pytest options. + mp.delenv("PYTEST_ADDOPTS", raising=False) + # Ensure no user config is used. + tmphome = str(self.path) + mp.setenv("HOME", tmphome) + mp.setenv("USERPROFILE", tmphome) + # Do not use colors for inner runs by default. + mp.setenv("PY_COLORS", "0") + + @property + def path(self) -> Path: + """Temporary directory path used to create files/run tests from, etc.""" + return self._path + + def __repr__(self) -> str: + return f"" + + def _finalize(self) -> None: + """ + Clean up global state artifacts. + + Some methods modify the global interpreter state and this tries to + clean this up. It does not remove the temporary directory however so + it can be looked at after the test run has finished. + """ + self._sys_modules_snapshot.restore() + self._sys_path_snapshot.restore() + + def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: + # Some zope modules used by twisted-related tests keep internal state + # and can't be deleted; we had some trouble in the past with + # `zope.interface` for example. + # + # Preserve readline due to https://bugs.python.org/issue41033. + # pexpect issues a SIGWINCH. + def preserve_module(name): + return name.startswith(("zope", "readline")) + + return SysModulesSnapshot(preserve=preserve_module) + + def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: + """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`.""" + pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True) # type: ignore[attr-defined] + self._request.addfinalizer(reprec.finish_recording) + return reprec + + def chdir(self) -> None: + """Cd into the temporary directory. + + This is done automatically upon instantiation. + """ + self._monkeypatch.chdir(self.path) + + def _makefile( + self, + ext: str, + lines: Sequence[Any | bytes], + files: dict[str, str], + encoding: str = "utf-8", + ) -> Path: + items = list(files.items()) + + if ext is None: + raise TypeError("ext must not be None") + + if ext and not ext.startswith("."): + raise ValueError( + f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" + ) + + def to_text(s: Any | bytes) -> str: + return s.decode(encoding) if isinstance(s, bytes) else str(s) + + if lines: + source = "\n".join(to_text(x) for x in lines) + basename = self._name + items.insert(0, (basename, source)) + + ret = None + for basename, value in items: + p = self.path.joinpath(basename).with_suffix(ext) + p.parent.mkdir(parents=True, exist_ok=True) + source_ = Source(value) + source = "\n".join(to_text(line) for line in source_.lines) + p.write_text(source.strip(), encoding=encoding) + if ret is None: + ret = p + assert ret is not None + return ret + + def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: + r"""Create new text file(s) in the test directory. + + :param ext: + The extension the file(s) should use, including the dot, e.g. `.py`. + :param args: + All args are treated as strings and joined using newlines. + The result is written as contents to the file. The name of the + file is based on the test function requesting this fixture. + :param kwargs: + Each keyword is the name of a file, while the value of it will + be written as contents of the file. + :returns: + The first created file. + + Examples: + + .. code-block:: python + + pytester.makefile(".txt", "line1", "line2") + + pytester.makefile(".ini", pytest="[pytest]\naddopts=-rs\n") + + To create binary files, use :meth:`pathlib.Path.write_bytes` directly: + + .. code-block:: python + + filename = pytester.path.joinpath("foo.bin") + filename.write_bytes(b"...") + """ + return self._makefile(ext, args, kwargs) + + def makeconftest(self, source: str) -> Path: + """Write a conftest.py file. + + :param source: The contents. + :returns: The conftest.py file. + """ + return self.makepyfile(conftest=source) + + def makeini(self, source: str) -> Path: + """Write a tox.ini file. + + :param source: The contents. + :returns: The tox.ini file. + """ + return self.makefile(".ini", tox=source) + + def getinicfg(self, source: str) -> SectionWrapper: + """Return the pytest section from the tox.ini config file.""" + p = self.makeini(source) + return IniConfig(str(p))["pytest"] + + def makepyprojecttoml(self, source: str) -> Path: + """Write a pyproject.toml file. + + :param source: The contents. + :returns: The pyproject.ini file. + + .. versionadded:: 6.0 + """ + return self.makefile(".toml", pyproject=source) + + def makepyfile(self, *args, **kwargs) -> Path: + r"""Shortcut for .makefile() with a .py extension. + + Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting + existing files. + + Examples: + + .. code-block:: python + + def test_something(pytester): + # Initial file is created test_something.py. + pytester.makepyfile("foobar") + # To create multiple files, pass kwargs accordingly. + pytester.makepyfile(custom="foobar") + # At this point, both 'test_something.py' & 'custom.py' exist in the test directory. + + """ + return self._makefile(".py", args, kwargs) + + def maketxtfile(self, *args, **kwargs) -> Path: + r"""Shortcut for .makefile() with a .txt extension. + + Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting + existing files. + + Examples: + + .. code-block:: python + + def test_something(pytester): + # Initial file is created test_something.txt. + pytester.maketxtfile("foobar") + # To create multiple files, pass kwargs accordingly. + pytester.maketxtfile(custom="foobar") + # At this point, both 'test_something.txt' & 'custom.txt' exist in the test directory. + + """ + return self._makefile(".txt", args, kwargs) + + def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: + """Prepend a directory to sys.path, defaults to :attr:`path`. + + This is undone automatically when this object dies at the end of each + test. + + :param path: + The path. + """ + if path is None: + path = self.path + + self._monkeypatch.syspath_prepend(str(path)) + + def mkdir(self, name: str | os.PathLike[str]) -> Path: + """Create a new (sub)directory. + + :param name: + The name of the directory, relative to the pytester path. + :returns: + The created directory. + :rtype: pathlib.Path + """ + p = self.path / name + p.mkdir() + return p + + def mkpydir(self, name: str | os.PathLike[str]) -> Path: + """Create a new python package. + + This creates a (sub)directory with an empty ``__init__.py`` file so it + gets recognised as a Python package. + """ + p = self.path / name + p.mkdir() + p.joinpath("__init__.py").touch() + return p + + def copy_example(self, name: str | None = None) -> Path: + """Copy file from project's directory into the testdir. + + :param name: + The name of the file to copy. + :return: + Path to the copied directory (inside ``self.path``). + :rtype: pathlib.Path + """ + example_dir_ = self._request.config.getini("pytester_example_dir") + if example_dir_ is None: + raise ValueError("pytester_example_dir is unset, can't copy examples") + example_dir: Path = self._request.config.rootpath / example_dir_ + + for extra_element in self._request.node.iter_markers("pytester_example_path"): + assert extra_element.args + example_dir = example_dir.joinpath(*extra_element.args) + + if name is None: + func_name = self._name + maybe_dir = example_dir / func_name + maybe_file = example_dir / (func_name + ".py") + + if maybe_dir.is_dir(): + example_path = maybe_dir + elif maybe_file.is_file(): + example_path = maybe_file + else: + raise LookupError( + f"{func_name} can't be found as module or package in {example_dir}" + ) + else: + example_path = example_dir.joinpath(name) + + if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): + shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True) + return self.path + elif example_path.is_file(): + result = self.path.joinpath(example_path.name) + shutil.copy(example_path, result) + return result + else: + raise LookupError( + f'example "{example_path}" is not found as a file or directory' + ) + + def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: + """Get the collection node of a file. + + :param config: + A pytest config. + See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it. + :param arg: + Path to the file. + :returns: + The node. + """ + session = Session.from_config(config) + assert "::" not in str(arg) + p = Path(os.path.abspath(arg)) + config.hook.pytest_sessionstart(session=session) + res = session.perform_collect([str(p)], genitems=False)[0] + config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) + return res + + def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item: + """Return the collection node of a file. + + This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to + create the (configured) pytest Config instance. + + :param path: + Path to the file. + :returns: + The node. + """ + path = Path(path) + config = self.parseconfigure(path) + session = Session.from_config(config) + x = bestrelpath(session.path, path) + config.hook.pytest_sessionstart(session=session) + res = session.perform_collect([x], genitems=False)[0] + config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) + return res + + def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]: + """Generate all test items from a collection node. + + This recurses into the collection node and returns a list of all the + test items contained within. + + :param colitems: + The collection nodes. + :returns: + The collected items. + """ + session = colitems[0].session + result: list[Item] = [] + for colitem in colitems: + result.extend(session.genitems(colitem)) + return result + + def runitem(self, source: str) -> Any: + """Run the "test_func" Item. + + The calling test instance (class containing the test method) must + provide a ``.getrunner()`` method which should return a runner which + can run the test protocol for a single item, e.g. + ``_pytest.runner.runtestprotocol``. + """ + # used from runner functional tests + item = self.getitem(source) + # the test class where we are called from wants to provide the runner + testclassinstance = self._request.instance + runner = testclassinstance.getrunner() + return runner(item) + + def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder: + """Run a test module in process using ``pytest.main()``. + + This run writes "source" into a temporary file and runs + ``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance + for the result. + + :param source: The source code of the test module. + :param cmdlineargs: Any extra command line arguments to use. + """ + p = self.makepyfile(source) + values = [*list(cmdlineargs), p] + return self.inline_run(*values) + + def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]: + """Run ``pytest.main(['--collect-only'])`` in-process. + + Runs the :py:func:`pytest.main` function to run all of pytest inside + the test process itself like :py:meth:`inline_run`, but returns a + tuple of the collected items and a :py:class:`HookRecorder` instance. + """ + rec = self.inline_run("--collect-only", *args) + items = [x.item for x in rec.getcalls("pytest_itemcollected")] + return items, rec + + def inline_run( + self, + *args: str | os.PathLike[str], + plugins=(), + no_reraise_ctrlc: bool = False, + ) -> HookRecorder: + """Run ``pytest.main()`` in-process, returning a HookRecorder. + + Runs the :py:func:`pytest.main` function to run all of pytest inside + the test process itself. This means it can return a + :py:class:`HookRecorder` instance which gives more detailed results + from that run than can be done by matching stdout/stderr from + :py:meth:`runpytest`. + + :param args: + Command line arguments to pass to :py:func:`pytest.main`. + :param plugins: + Extra plugin instances the ``pytest.main()`` instance should use. + :param no_reraise_ctrlc: + Typically we reraise keyboard interrupts from the child run. If + True, the KeyboardInterrupt exception is captured. + """ + # (maybe a cpython bug?) the importlib cache sometimes isn't updated + # properly between file creation and inline_run (especially if imports + # are interspersed with file creation) + importlib.invalidate_caches() + + plugins = list(plugins) + finalizers = [] + try: + # Any sys.module or sys.path changes done while running pytest + # inline should be reverted after the test run completes to avoid + # clashing with later inline tests run within the same pytest test, + # e.g. just because they use matching test module names. + finalizers.append(self.__take_sys_modules_snapshot().restore) + finalizers.append(SysPathsSnapshot().restore) + + # Important note: + # - our tests should not leave any other references/registrations + # laying around other than possibly loaded test modules + # referenced from sys.modules, as nothing will clean those up + # automatically + + rec = [] + + class Collect: + def pytest_configure(x, config: Config) -> None: + rec.append(self.make_hook_recorder(config.pluginmanager)) + + plugins.append(Collect()) + ret = main([str(x) for x in args], plugins=plugins) + if len(rec) == 1: + reprec = rec.pop() + else: + + class reprec: # type: ignore + pass + + reprec.ret = ret + + # Typically we reraise keyboard interrupts from the child run + # because it's our user requesting interruption of the testing. + if ret == ExitCode.INTERRUPTED and not no_reraise_ctrlc: + calls = reprec.getcalls("pytest_keyboard_interrupt") + if calls and calls[-1].excinfo.type == KeyboardInterrupt: + raise KeyboardInterrupt() + return reprec + finally: + for finalizer in finalizers: + finalizer() + + def runpytest_inprocess( + self, *args: str | os.PathLike[str], **kwargs: Any + ) -> RunResult: + """Return result of running pytest in-process, providing a similar + interface to what self.runpytest() provides.""" + syspathinsert = kwargs.pop("syspathinsert", False) + + if syspathinsert: + self.syspathinsert() + now = timing.time() + capture = _get_multicapture("sys") + capture.start_capturing() + try: + try: + reprec = self.inline_run(*args, **kwargs) + except SystemExit as e: + ret = e.args[0] + try: + ret = ExitCode(e.args[0]) + except ValueError: + pass + + class reprec: # type: ignore + ret = ret + + except Exception: + traceback.print_exc() + + class reprec: # type: ignore + ret = ExitCode(3) + + finally: + out, err = capture.readouterr() + capture.stop_capturing() + sys.stdout.write(out) + sys.stderr.write(err) + + assert reprec.ret is not None + res = RunResult( + reprec.ret, out.splitlines(), err.splitlines(), timing.time() - now + ) + res.reprec = reprec # type: ignore + return res + + def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult: + """Run pytest inline or in a subprocess, depending on the command line + option "--runpytest" and return a :py:class:`~pytest.RunResult`.""" + new_args = self._ensure_basetemp(args) + if self._method == "inprocess": + return self.runpytest_inprocess(*new_args, **kwargs) + elif self._method == "subprocess": + return self.runpytest_subprocess(*new_args, **kwargs) + raise RuntimeError(f"Unrecognized runpytest option: {self._method}") + + def _ensure_basetemp( + self, args: Sequence[str | os.PathLike[str]] + ) -> list[str | os.PathLike[str]]: + new_args = list(args) + for x in new_args: + if str(x).startswith("--basetemp"): + break + else: + new_args.append( + "--basetemp={}".format(self.path.parent.joinpath("basetemp")) + ) + return new_args + + def parseconfig(self, *args: str | os.PathLike[str]) -> Config: + """Return a new pytest :class:`pytest.Config` instance from given + commandline args. + + This invokes the pytest bootstrapping code in _pytest.config to create a + new :py:class:`pytest.PytestPluginManager` and call the + :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config` + instance. + + If :attr:`plugins` has been populated they should be plugin modules + to be registered with the plugin manager. + """ + import _pytest.config + + new_args = self._ensure_basetemp(args) + new_args = [str(x) for x in new_args] + + config = _pytest.config._prepareconfig(new_args, self.plugins) # type: ignore[arg-type] + # we don't know what the test will do with this half-setup config + # object and thus we make sure it gets unconfigured properly in any + # case (otherwise capturing could still be active, for example) + self._request.addfinalizer(config._ensure_unconfigure) + return config + + def parseconfigure(self, *args: str | os.PathLike[str]) -> Config: + """Return a new pytest configured Config instance. + + Returns a new :py:class:`pytest.Config` instance like + :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure` + hook. + """ + config = self.parseconfig(*args) + config._do_configure() + return config + + def getitem( + self, source: str | os.PathLike[str], funcname: str = "test_func" + ) -> Item: + """Return the test item for a test function. + + Writes the source to a python file and runs pytest's collection on + the resulting module, returning the test item for the requested + function name. + + :param source: + The module source. + :param funcname: + The name of the test function for which to return a test item. + :returns: + The test item. + """ + items = self.getitems(source) + for item in items: + if item.name == funcname: + return item + assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}" + + def getitems(self, source: str | os.PathLike[str]) -> list[Item]: + """Return all test items collected from the module. + + Writes the source to a Python file and runs pytest's collection on + the resulting module, returning all test items contained within. + """ + modcol = self.getmodulecol(source) + return self.genitems([modcol]) + + def getmodulecol( + self, + source: str | os.PathLike[str], + configargs=(), + *, + withinit: bool = False, + ): + """Return the module collection node for ``source``. + + Writes ``source`` to a file using :py:meth:`makepyfile` and then + runs the pytest collection on it, returning the collection node for the + test module. + + :param source: + The source code of the module to collect. + + :param configargs: + Any extra arguments to pass to :py:meth:`parseconfigure`. + + :param withinit: + Whether to also write an ``__init__.py`` file to the same + directory to ensure it is a package. + """ + if isinstance(source, os.PathLike): + path = self.path.joinpath(source) + assert not withinit, "not supported for paths" + else: + kw = {self._name: str(source)} + path = self.makepyfile(**kw) + if withinit: + self.makepyfile(__init__="#") + self.config = config = self.parseconfigure(path, *configargs) + return self.getnode(config, path) + + def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: + """Return the collection node for name from the module collection. + + Searches a module collection node for a collection node matching the + given name. + + :param modcol: A module collection node; see :py:meth:`getmodulecol`. + :param name: The name of the node to return. + """ + if modcol not in self._mod_collections: + self._mod_collections[modcol] = list(modcol.collect()) + for colitem in self._mod_collections[modcol]: + if colitem.name == name: + return colitem + return None + + def popen( + self, + cmdargs: Sequence[str | os.PathLike[str]], + stdout: int | TextIO = subprocess.PIPE, + stderr: int | TextIO = subprocess.PIPE, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + **kw, + ): + """Invoke :py:class:`subprocess.Popen`. + + Calls :py:class:`subprocess.Popen` making sure the current working + directory is in ``PYTHONPATH``. + + You probably want to use :py:meth:`run` instead. + """ + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join( + filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) + ) + kw["env"] = env + + if stdin is self.CLOSE_STDIN: + kw["stdin"] = subprocess.PIPE + elif isinstance(stdin, bytes): + kw["stdin"] = subprocess.PIPE + else: + kw["stdin"] = stdin + + popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) + if stdin is self.CLOSE_STDIN: + assert popen.stdin is not None + popen.stdin.close() + elif isinstance(stdin, bytes): + assert popen.stdin is not None + popen.stdin.write(stdin) + + return popen + + def run( + self, + *cmdargs: str | os.PathLike[str], + timeout: float | None = None, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + ) -> RunResult: + """Run a command with arguments. + + Run a process using :py:class:`subprocess.Popen` saving the stdout and + stderr. + + :param cmdargs: + The sequence of arguments to pass to :py:class:`subprocess.Popen`, + with path-like objects being converted to :py:class:`str` + automatically. + :param timeout: + The period in seconds after which to timeout and raise + :py:class:`Pytester.TimeoutExpired`. + :param stdin: + Optional standard input. + + - If it is ``CLOSE_STDIN`` (Default), then this method calls + :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and + the standard input is closed immediately after the new command is + started. + + - If it is of type :py:class:`bytes`, these bytes are sent to the + standard input of the command. + + - Otherwise, it is passed through to :py:class:`subprocess.Popen`. + For further information in this case, consult the document of the + ``stdin`` parameter in :py:class:`subprocess.Popen`. + :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int + :returns: + The result. + + """ + __tracebackhide__ = True + + cmdargs = tuple(os.fspath(arg) for arg in cmdargs) + p1 = self.path.joinpath("stdout") + p2 = self.path.joinpath("stderr") + print("running:", *cmdargs) + print(" in:", Path.cwd()) + + with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2: + now = timing.time() + popen = self.popen( + cmdargs, + stdin=stdin, + stdout=f1, + stderr=f2, + close_fds=(sys.platform != "win32"), + ) + if popen.stdin is not None: + popen.stdin.close() + + def handle_timeout() -> None: + __tracebackhide__ = True + + timeout_message = f"{timeout} second timeout expired running: {cmdargs}" + + popen.kill() + popen.wait() + raise self.TimeoutExpired(timeout_message) + + if timeout is None: + ret = popen.wait() + else: + try: + ret = popen.wait(timeout) + except subprocess.TimeoutExpired: + handle_timeout() + + with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2: + out = f1.read().splitlines() + err = f2.read().splitlines() + + self._dump_lines(out, sys.stdout) + self._dump_lines(err, sys.stderr) + + with contextlib.suppress(ValueError): + ret = ExitCode(ret) + return RunResult(ret, out, err, timing.time() - now) + + def _dump_lines(self, lines, fp): + try: + for line in lines: + print(line, file=fp) + except UnicodeEncodeError: + print(f"couldn't print to {fp} because of encoding") + + def _getpytestargs(self) -> tuple[str, ...]: + return sys.executable, "-mpytest" + + def runpython(self, script: os.PathLike[str]) -> RunResult: + """Run a python script using sys.executable as interpreter.""" + return self.run(sys.executable, script) + + def runpython_c(self, command: str) -> RunResult: + """Run ``python -c "command"``.""" + return self.run(sys.executable, "-c", command) + + def runpytest_subprocess( + self, *args: str | os.PathLike[str], timeout: float | None = None + ) -> RunResult: + """Run pytest as a subprocess with given arguments. + + Any plugins added to the :py:attr:`plugins` list will be added using the + ``-p`` command line option. Additionally ``--basetemp`` is used to put + any temporary files and directories in a numbered directory prefixed + with "runpytest-" to not conflict with the normal numbered pytest + location for temporary files and directories. + + :param args: + The sequence of arguments to pass to the pytest subprocess. + :param timeout: + The period in seconds after which to timeout and raise + :py:class:`Pytester.TimeoutExpired`. + :returns: + The result. + """ + __tracebackhide__ = True + p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) + args = (f"--basetemp={p}", *args) + plugins = [x for x in self.plugins if isinstance(x, str)] + if plugins: + args = ("-p", plugins[0], *args) + args = self._getpytestargs() + args + return self.run(*args, timeout=timeout) + + def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: + """Run pytest using pexpect. + + This makes sure to use the right pytest and sets up the temporary + directory locations. + + The pexpect child is returned. + """ + basetemp = self.path / "temp-pexpect" + basetemp.mkdir(mode=0o700) + invoke = " ".join(map(str, self._getpytestargs())) + cmd = f"{invoke} --basetemp={basetemp} {string}" + return self.spawn(cmd, expect_timeout=expect_timeout) + + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: + """Run a command using pexpect. + + The pexpect child is returned. + """ + pexpect = importorskip("pexpect", "3.0") + if hasattr(sys, "pypy_version_info") and "64" in platform.machine(): + skip("pypy-64 bit not supported") + if not hasattr(pexpect, "spawn"): + skip("pexpect.spawn not available") + logfile = self.path.joinpath("spawn.out").open("wb") + + child = pexpect.spawn(cmd, logfile=logfile, timeout=expect_timeout) + self._request.addfinalizer(logfile.close) + return child + + +class LineComp: + def __init__(self) -> None: + self.stringio = StringIO() + """:class:`python:io.StringIO()` instance used for input.""" + + def assert_contains_lines(self, lines2: Sequence[str]) -> None: + """Assert that ``lines2`` are contained (linearly) in :attr:`stringio`'s value. + + Lines are matched using :func:`LineMatcher.fnmatch_lines `. + """ + __tracebackhide__ = True + val = self.stringio.getvalue() + self.stringio.truncate(0) + self.stringio.seek(0) + lines1 = val.split("\n") + LineMatcher(lines1).fnmatch_lines(lines2) + + +class LineMatcher: + """Flexible matching of text. + + This is a convenience class to test large texts like the output of + commands. + + The constructor takes a list of lines without their trailing newlines, i.e. + ``text.splitlines()``. + """ + + def __init__(self, lines: list[str]) -> None: + self.lines = lines + self._log_output: list[str] = [] + + def __str__(self) -> str: + """Return the entire original text. + + .. versionadded:: 6.2 + You can use :meth:`str` in older versions. + """ + return "\n".join(self.lines) + + def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]: + if isinstance(lines2, str): + lines2 = Source(lines2) + if isinstance(lines2, Source): + lines2 = lines2.strip().lines + return lines2 + + def fnmatch_lines_random(self, lines2: Sequence[str]) -> None: + """Check lines exist in the output in any order (using :func:`python:fnmatch.fnmatch`).""" + __tracebackhide__ = True + self._match_lines_random(lines2, fnmatch) + + def re_match_lines_random(self, lines2: Sequence[str]) -> None: + """Check lines exist in the output in any order (using :func:`python:re.match`).""" + __tracebackhide__ = True + self._match_lines_random(lines2, lambda name, pat: bool(re.match(pat, name))) + + def _match_lines_random( + self, lines2: Sequence[str], match_func: Callable[[str, str], bool] + ) -> None: + __tracebackhide__ = True + lines2 = self._getlines(lines2) + for line in lines2: + for x in self.lines: + if line == x or match_func(x, line): + self._log("matched: ", repr(line)) + break + else: + msg = f"line {line!r} not found in output" + self._log(msg) + self._fail(msg) + + def get_lines_after(self, fnline: str) -> Sequence[str]: + """Return all lines following the given line in the text. + + The given line can contain glob wildcards. + """ + for i, line in enumerate(self.lines): + if fnline == line or fnmatch(line, fnline): + return self.lines[i + 1 :] + raise ValueError(f"line {fnline!r} not found in output") + + def _log(self, *args) -> None: + self._log_output.append(" ".join(str(x) for x in args)) + + @property + def _log_text(self) -> str: + return "\n".join(self._log_output) + + def fnmatch_lines( + self, lines2: Sequence[str], *, consecutive: bool = False + ) -> None: + """Check lines exist in the output (using :func:`python:fnmatch.fnmatch`). + + The argument is a list of lines which have to match and can use glob + wildcards. If they do not match a pytest.fail() is called. The + matches and non-matches are also shown as part of the error message. + + :param lines2: String patterns to match. + :param consecutive: Match lines consecutively? + """ + __tracebackhide__ = True + self._match_lines(lines2, fnmatch, "fnmatch", consecutive=consecutive) + + def re_match_lines( + self, lines2: Sequence[str], *, consecutive: bool = False + ) -> None: + """Check lines exist in the output (using :func:`python:re.match`). + + The argument is a list of lines which have to match using ``re.match``. + If they do not match a pytest.fail() is called. + + The matches and non-matches are also shown as part of the error message. + + :param lines2: string patterns to match. + :param consecutive: match lines consecutively? + """ + __tracebackhide__ = True + self._match_lines( + lines2, + lambda name, pat: bool(re.match(pat, name)), + "re.match", + consecutive=consecutive, + ) + + def _match_lines( + self, + lines2: Sequence[str], + match_func: Callable[[str, str], bool], + match_nickname: str, + *, + consecutive: bool = False, + ) -> None: + """Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``. + + :param Sequence[str] lines2: + List of string patterns to match. The actual format depends on + ``match_func``. + :param match_func: + A callable ``match_func(line, pattern)`` where line is the + captured line from stdout/stderr and pattern is the matching + pattern. + :param str match_nickname: + The nickname for the match function that will be logged to stdout + when a match occurs. + :param consecutive: + Match lines consecutively? + """ + if not isinstance(lines2, collections.abc.Sequence): + raise TypeError(f"invalid type for lines2: {type(lines2).__name__}") + lines2 = self._getlines(lines2) + lines1 = self.lines[:] + extralines = [] + __tracebackhide__ = True + wnick = len(match_nickname) + 1 + started = False + for line in lines2: + nomatchprinted = False + while lines1: + nextline = lines1.pop(0) + if line == nextline: + self._log("exact match:", repr(line)) + started = True + break + elif match_func(nextline, line): + self._log(f"{match_nickname}:", repr(line)) + self._log( + "{:>{width}}".format("with:", width=wnick), repr(nextline) + ) + started = True + break + else: + if consecutive and started: + msg = f"no consecutive match: {line!r}" + self._log(msg) + self._log( + "{:>{width}}".format("with:", width=wnick), repr(nextline) + ) + self._fail(msg) + if not nomatchprinted: + self._log( + "{:>{width}}".format("nomatch:", width=wnick), repr(line) + ) + nomatchprinted = True + self._log("{:>{width}}".format("and:", width=wnick), repr(nextline)) + extralines.append(nextline) + else: + msg = f"remains unmatched: {line!r}" + self._log(msg) + self._fail(msg) + self._log_output = [] + + def no_fnmatch_line(self, pat: str) -> None: + """Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``. + + :param str pat: The pattern to match lines. + """ + __tracebackhide__ = True + self._no_match_line(pat, fnmatch, "fnmatch") + + def no_re_match_line(self, pat: str) -> None: + """Ensure captured lines do not match the given pattern, using ``re.match``. + + :param str pat: The regular expression to match lines. + """ + __tracebackhide__ = True + self._no_match_line( + pat, lambda name, pat: bool(re.match(pat, name)), "re.match" + ) + + def _no_match_line( + self, pat: str, match_func: Callable[[str, str], bool], match_nickname: str + ) -> None: + """Ensure captured lines does not have a the given pattern, using ``fnmatch.fnmatch``. + + :param str pat: The pattern to match lines. + """ + __tracebackhide__ = True + nomatch_printed = False + wnick = len(match_nickname) + 1 + for line in self.lines: + if match_func(line, pat): + msg = f"{match_nickname}: {pat!r}" + self._log(msg) + self._log("{:>{width}}".format("with:", width=wnick), repr(line)) + self._fail(msg) + else: + if not nomatch_printed: + self._log("{:>{width}}".format("nomatch:", width=wnick), repr(pat)) + nomatch_printed = True + self._log("{:>{width}}".format("and:", width=wnick), repr(line)) + self._log_output = [] + + def _fail(self, msg: str) -> None: + __tracebackhide__ = True + log_text = self._log_text + self._log_output = [] + fail(log_text) + + def str(self) -> str: + """Return the entire original text.""" + return str(self) diff --git a/.venv/Lib/site-packages/_pytest/pytester_assertions.py b/.venv/Lib/site-packages/_pytest/pytester_assertions.py new file mode 100644 index 0000000..d543798 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/pytester_assertions.py @@ -0,0 +1,74 @@ +"""Helper plugin for pytester; should not be loaded on its own.""" + +# This plugin contains assertions used by pytester. pytester cannot +# contain them itself, since it is imported by the `pytest` module, +# hence cannot be subject to assertion rewriting, which requires a +# module to not be already imported. +from __future__ import annotations + +from typing import Sequence + +from _pytest.reports import CollectReport +from _pytest.reports import TestReport + + +def assertoutcome( + outcomes: tuple[ + Sequence[TestReport], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], + ], + passed: int = 0, + skipped: int = 0, + failed: int = 0, +) -> None: + __tracebackhide__ = True + + realpassed, realskipped, realfailed = outcomes + obtained = { + "passed": len(realpassed), + "skipped": len(realskipped), + "failed": len(realfailed), + } + expected = {"passed": passed, "skipped": skipped, "failed": failed} + assert obtained == expected, outcomes + + +def assert_outcomes( + outcomes: dict[str, int], + passed: int = 0, + skipped: int = 0, + failed: int = 0, + errors: int = 0, + xpassed: int = 0, + xfailed: int = 0, + warnings: int | None = None, + deselected: int | None = None, +) -> None: + """Assert that the specified outcomes appear with the respective + numbers (0 means it didn't occur) in the text output from a test run.""" + __tracebackhide__ = True + + obtained = { + "passed": outcomes.get("passed", 0), + "skipped": outcomes.get("skipped", 0), + "failed": outcomes.get("failed", 0), + "errors": outcomes.get("errors", 0), + "xpassed": outcomes.get("xpassed", 0), + "xfailed": outcomes.get("xfailed", 0), + } + expected = { + "passed": passed, + "skipped": skipped, + "failed": failed, + "errors": errors, + "xpassed": xpassed, + "xfailed": xfailed, + } + if warnings is not None: + obtained["warnings"] = outcomes.get("warnings", 0) + expected["warnings"] = warnings + if deselected is not None: + obtained["deselected"] = outcomes.get("deselected", 0) + expected["deselected"] = deselected + assert obtained == expected diff --git a/.venv/Lib/site-packages/_pytest/python.py b/.venv/Lib/site-packages/_pytest/python.py new file mode 100644 index 0000000..3478c34 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/python.py @@ -0,0 +1,1679 @@ +# mypy: allow-untyped-defs +"""Python test discovery, setup and run of test functions.""" + +from __future__ import annotations + +import abc +from collections import Counter +from collections import defaultdict +import dataclasses +import enum +import fnmatch +from functools import partial +import inspect +import itertools +import os +from pathlib import Path +import types +from typing import Any +from typing import Callable +from typing import Dict +from typing import final +from typing import Generator +from typing import Iterable +from typing import Iterator +from typing import Literal +from typing import Mapping +from typing import Pattern +from typing import Sequence +from typing import TYPE_CHECKING +import warnings + +import _pytest +from _pytest import fixtures +from _pytest import nodes +from _pytest._code import filter_traceback +from _pytest._code import getfslineno +from _pytest._code.code import ExceptionInfo +from _pytest._code.code import TerminalRepr +from _pytest._code.code import Traceback +from _pytest._io.saferepr import saferepr +from _pytest.compat import ascii_escaped +from _pytest.compat import get_default_arg_names +from _pytest.compat import get_real_func +from _pytest.compat import getimfunc +from _pytest.compat import is_async_function +from _pytest.compat import is_generator +from _pytest.compat import LEGACY_PATH +from _pytest.compat import NOTSET +from _pytest.compat import safe_getattr +from _pytest.compat import safe_isclass +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest +from _pytest.fixtures import FixtureDef +from _pytest.fixtures import FixtureRequest +from _pytest.fixtures import FuncFixtureInfo +from _pytest.fixtures import get_scope_node +from _pytest.main import Session +from _pytest.mark import MARK_GEN +from _pytest.mark import ParameterSet +from _pytest.mark.structures import get_unpacked_marks +from _pytest.mark.structures import Mark +from _pytest.mark.structures import MarkDecorator +from _pytest.mark.structures import normalize_mark_list +from _pytest.outcomes import fail +from _pytest.outcomes import skip +from _pytest.pathlib import fnmatch_ex +from _pytest.pathlib import import_path +from _pytest.pathlib import ImportPathMismatchError +from _pytest.pathlib import scandir +from _pytest.scope import _ScopeName +from _pytest.scope import Scope +from _pytest.stash import StashKey +from _pytest.warning_types import PytestCollectionWarning +from _pytest.warning_types import PytestReturnNotNoneWarning +from _pytest.warning_types import PytestUnhandledCoroutineWarning + + +if TYPE_CHECKING: + from typing_extensions import Self + + +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "python_files", + type="args", + # NOTE: default is also used in AssertionRewritingHook. + default=["test_*.py", "*_test.py"], + help="Glob-style file patterns for Python test module discovery", + ) + parser.addini( + "python_classes", + type="args", + default=["Test"], + help="Prefixes or glob names for Python test class discovery", + ) + parser.addini( + "python_functions", + type="args", + default=["test"], + help="Prefixes or glob names for Python test function and method discovery", + ) + parser.addini( + "disable_test_id_escaping_and_forfeit_all_rights_to_community_support", + type="bool", + default=False, + help="Disable string escape non-ASCII characters, might cause unwanted " + "side effects(use at your own risk)", + ) + + +def pytest_generate_tests(metafunc: Metafunc) -> None: + for marker in metafunc.definition.iter_markers(name="parametrize"): + metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) + + +def pytest_configure(config: Config) -> None: + config.addinivalue_line( + "markers", + "parametrize(argnames, argvalues): call a test function multiple " + "times passing in different arguments in turn. argvalues generally " + "needs to be a list of values if argnames specifies only one name " + "or a list of tuples of values if argnames specifies multiple names. " + "Example: @parametrize('arg1', [1,2]) would lead to two calls of the " + "decorated test function, one with arg1=1 and another with arg1=2." + "see https://docs.pytest.org/en/stable/how-to/parametrize.html for more info " + "and examples.", + ) + config.addinivalue_line( + "markers", + "usefixtures(fixturename1, fixturename2, ...): mark tests as needing " + "all of the specified fixtures. see " + "https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures ", + ) + + +def async_warn_and_skip(nodeid: str) -> None: + msg = "async def functions are not natively supported and have been skipped.\n" + msg += ( + "You need to install a suitable plugin for your async framework, for example:\n" + ) + msg += " - anyio\n" + msg += " - pytest-asyncio\n" + msg += " - pytest-tornasync\n" + msg += " - pytest-trio\n" + msg += " - pytest-twisted" + warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid))) + skip(reason="async def function and no async plugin installed (see warnings)") + + +@hookimpl(trylast=True) +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: + testfunction = pyfuncitem.obj + if is_async_function(testfunction): + async_warn_and_skip(pyfuncitem.nodeid) + funcargs = pyfuncitem.funcargs + testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} + result = testfunction(**testargs) + if hasattr(result, "__await__") or hasattr(result, "__aiter__"): + async_warn_and_skip(pyfuncitem.nodeid) + elif result is not None: + warnings.warn( + PytestReturnNotNoneWarning( + f"Expected None, but {pyfuncitem.nodeid} returned {result!r}, which will be an error in a " + "future version of pytest. Did you mean to use `assert` instead of `return`?" + ) + ) + return True + + +def pytest_collect_directory( + path: Path, parent: nodes.Collector +) -> nodes.Collector | None: + pkginit = path / "__init__.py" + try: + has_pkginit = pkginit.is_file() + except PermissionError: + # See https://github.com/pytest-dev/pytest/issues/12120#issuecomment-2106349096. + return None + if has_pkginit: + return Package.from_parent(parent, path=path) + return None + + +def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Module | None: + if file_path.suffix == ".py": + if not parent.session.isinitpath(file_path): + if not path_matches_patterns( + file_path, parent.config.getini("python_files") + ): + return None + ihook = parent.session.gethookproxy(file_path) + module: Module = ihook.pytest_pycollect_makemodule( + module_path=file_path, parent=parent + ) + return module + return None + + +def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: + """Return whether path matches any of the patterns in the list of globs given.""" + return any(fnmatch_ex(pattern, path) for pattern in patterns) + + +def pytest_pycollect_makemodule(module_path: Path, parent) -> Module: + return Module.from_parent(parent, path=module_path) + + +@hookimpl(trylast=True) +def pytest_pycollect_makeitem( + collector: Module | Class, name: str, obj: object +) -> None | nodes.Item | nodes.Collector | list[nodes.Item | nodes.Collector]: + assert isinstance(collector, (Class, Module)), type(collector) + # Nothing was collected elsewhere, let's do it here. + if safe_isclass(obj): + if collector.istestclass(obj, name): + return Class.from_parent(collector, name=name, obj=obj) + elif collector.istestfunction(obj, name): + # mock seems to store unbound methods (issue473), normalize it. + obj = getattr(obj, "__func__", obj) + # We need to try and unwrap the function if it's a functools.partial + # or a functools.wrapped. + # We mustn't if it's been wrapped with mock.patch (python 2 only). + if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))): + filename, lineno = getfslineno(obj) + warnings.warn_explicit( + message=PytestCollectionWarning( + f"cannot collect {name!r} because it is not a function." + ), + category=None, + filename=str(filename), + lineno=lineno + 1, + ) + elif getattr(obj, "__test__", True): + if is_generator(obj): + res = Function.from_parent(collector, name=name) + reason = ( + f"yield tests were removed in pytest 4.0 - {name} will be ignored" + ) + res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) + res.warn(PytestCollectionWarning(reason)) + return res + else: + return list(collector._genfunctions(name, obj)) + return None + + +class PyobjMixin(nodes.Node): + """this mix-in inherits from Node to carry over the typing information + + as its intended to always mix in before a node + its position in the mro is unaffected""" + + _ALLOW_MARKERS = True + + @property + def module(self): + """Python module object this node was collected from (can be None).""" + node = self.getparent(Module) + return node.obj if node is not None else None + + @property + def cls(self): + """Python class object this node was collected from (can be None).""" + node = self.getparent(Class) + return node.obj if node is not None else None + + @property + def instance(self): + """Python instance object the function is bound to. + + Returns None if not a test method, e.g. for a standalone test function, + a class or a module. + """ + # Overridden by Function. + return None + + @property + def obj(self): + """Underlying Python object.""" + obj = getattr(self, "_obj", None) + if obj is None: + self._obj = obj = self._getobj() + # XXX evil hack + # used to avoid Function marker duplication + if self._ALLOW_MARKERS: + self.own_markers.extend(get_unpacked_marks(self.obj)) + # This assumes that `obj` is called before there is a chance + # to add custom keys to `self.keywords`, so no fear of overriding. + self.keywords.update((mark.name, mark) for mark in self.own_markers) + return obj + + @obj.setter + def obj(self, value): + self._obj = value + + def _getobj(self): + """Get the underlying Python object. May be overwritten by subclasses.""" + # TODO: Improve the type of `parent` such that assert/ignore aren't needed. + assert self.parent is not None + obj = self.parent.obj # type: ignore[attr-defined] + return getattr(obj, self.name) + + def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: + """Return Python path relative to the containing module.""" + parts = [] + for node in self.iter_parents(): + name = node.name + if isinstance(node, Module): + name = os.path.splitext(name)[0] + if stopatmodule: + if includemodule: + parts.append(name) + break + parts.append(name) + parts.reverse() + return ".".join(parts) + + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: + # XXX caching? + path, lineno = getfslineno(self.obj) + modpath = self.getmodpath() + return path, lineno, modpath + + +# As an optimization, these builtin attribute names are pre-ignored when +# iterating over an object during collection -- the pytest_pycollect_makeitem +# hook is not called for them. +# fmt: off +class _EmptyClass: pass # noqa: E701 +IGNORED_ATTRIBUTES = frozenset.union( + frozenset(), + # Module. + dir(types.ModuleType("empty_module")), + # Some extra module attributes the above doesn't catch. + {"__builtins__", "__file__", "__cached__"}, + # Class. + dir(_EmptyClass), + # Instance. + dir(_EmptyClass()), +) +del _EmptyClass +# fmt: on + + +class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): + def funcnamefilter(self, name: str) -> bool: + return self._matches_prefix_or_glob_option("python_functions", name) + + def isnosetest(self, obj: object) -> bool: + """Look for the __test__ attribute, which is applied by the + @nose.tools.istest decorator. + """ + # We explicitly check for "is True" here to not mistakenly treat + # classes with a custom __getattr__ returning something truthy (like a + # function) as test classes. + return safe_getattr(obj, "__test__", False) is True + + def classnamefilter(self, name: str) -> bool: + return self._matches_prefix_or_glob_option("python_classes", name) + + def istestfunction(self, obj: object, name: str) -> bool: + if self.funcnamefilter(name) or self.isnosetest(obj): + if isinstance(obj, (staticmethod, classmethod)): + # staticmethods and classmethods need to be unwrapped. + obj = safe_getattr(obj, "__func__", False) + return callable(obj) and fixtures.getfixturemarker(obj) is None + else: + return False + + def istestclass(self, obj: object, name: str) -> bool: + if not (self.classnamefilter(name) or self.isnosetest(obj)): + return False + if inspect.isabstract(obj): + return False + return True + + def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: + """Check if the given name matches the prefix or glob-pattern defined + in ini configuration.""" + for option in self.config.getini(option_name): + if name.startswith(option): + return True + # Check that name looks like a glob-string before calling fnmatch + # because this is called for every name in each collected module, + # and fnmatch is somewhat expensive to call. + elif ("*" in option or "?" in option or "[" in option) and fnmatch.fnmatch( + name, option + ): + return True + return False + + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: + if not getattr(self.obj, "__test__", True): + return [] + + # Avoid random getattrs and peek in the __dict__ instead. + dicts = [getattr(self.obj, "__dict__", {})] + if isinstance(self.obj, type): + for basecls in self.obj.__mro__: + dicts.append(basecls.__dict__) + + # In each class, nodes should be definition ordered. + # __dict__ is definition ordered. + seen: set[str] = set() + dict_values: list[list[nodes.Item | nodes.Collector]] = [] + ihook = self.ihook + for dic in dicts: + values: list[nodes.Item | nodes.Collector] = [] + # Note: seems like the dict can change during iteration - + # be careful not to remove the list() without consideration. + for name, obj in list(dic.items()): + if name in IGNORED_ATTRIBUTES: + continue + if name in seen: + continue + seen.add(name) + res = ihook.pytest_pycollect_makeitem( + collector=self, name=name, obj=obj + ) + if res is None: + continue + elif isinstance(res, list): + values.extend(res) + else: + values.append(res) + dict_values.append(values) + + # Between classes in the class hierarchy, reverse-MRO order -- nodes + # inherited from base classes should come before subclasses. + result = [] + for values in reversed(dict_values): + result.extend(values) + return result + + def _genfunctions(self, name: str, funcobj) -> Iterator[Function]: + modulecol = self.getparent(Module) + assert modulecol is not None + module = modulecol.obj + clscol = self.getparent(Class) + cls = clscol and clscol.obj or None + + definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj) + fixtureinfo = definition._fixtureinfo + + # pytest_generate_tests impls call metafunc.parametrize() which fills + # metafunc._calls, the outcome of the hook. + metafunc = Metafunc( + definition=definition, + fixtureinfo=fixtureinfo, + config=self.config, + cls=cls, + module=module, + _ispytest=True, + ) + methods = [] + if hasattr(module, "pytest_generate_tests"): + methods.append(module.pytest_generate_tests) + if cls is not None and hasattr(cls, "pytest_generate_tests"): + methods.append(cls().pytest_generate_tests) + self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc)) + + if not metafunc._calls: + yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo) + else: + # Direct parametrizations taking place in module/class-specific + # `metafunc.parametrize` calls may have shadowed some fixtures, so make sure + # we update what the function really needs a.k.a its fixture closure. Note that + # direct parametrizations using `@pytest.mark.parametrize` have already been considered + # into making the closure using `ignore_args` arg to `getfixtureclosure`. + fixtureinfo.prune_dependency_tree() + + for callspec in metafunc._calls: + subname = f"{name}[{callspec.id}]" + yield Function.from_parent( + self, + name=subname, + callspec=callspec, + fixtureinfo=fixtureinfo, + keywords={callspec.id: True}, + originalname=name, + ) + + +def importtestmodule( + path: Path, + config: Config, +): + # We assume we are only called once per module. + importmode = config.getoption("--import-mode") + try: + mod = import_path( + path, + mode=importmode, + root=config.rootpath, + consider_namespace_packages=config.getini("consider_namespace_packages"), + ) + except SyntaxError as e: + raise nodes.Collector.CollectError( + ExceptionInfo.from_current().getrepr(style="short") + ) from e + except ImportPathMismatchError as e: + raise nodes.Collector.CollectError( + "import file mismatch:\n" + "imported module {!r} has this __file__ attribute:\n" + " {}\n" + "which is not the same as the test file we want to collect:\n" + " {}\n" + "HINT: remove __pycache__ / .pyc files and/or use a " + "unique basename for your test file modules".format(*e.args) + ) from e + except ImportError as e: + exc_info = ExceptionInfo.from_current() + if config.get_verbosity() < 2: + exc_info.traceback = exc_info.traceback.filter(filter_traceback) + exc_repr = ( + exc_info.getrepr(style="short") + if exc_info.traceback + else exc_info.exconly() + ) + formatted_tb = str(exc_repr) + raise nodes.Collector.CollectError( + f"ImportError while importing test module '{path}'.\n" + "Hint: make sure your test modules/packages have valid Python names.\n" + "Traceback:\n" + f"{formatted_tb}" + ) from e + except skip.Exception as e: + if e.allow_module_level: + raise + raise nodes.Collector.CollectError( + "Using pytest.skip outside of a test will skip the entire module. " + "If that's your intention, pass `allow_module_level=True`. " + "If you want to skip a specific test or an entire class, " + "use the @pytest.mark.skip or @pytest.mark.skipif decorators." + ) from e + config.pluginmanager.consider_module(mod) + return mod + + +class Module(nodes.File, PyCollector): + """Collector for test classes and functions in a Python module.""" + + def _getobj(self): + return importtestmodule(self.path, self.config) + + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: + self._register_setup_module_fixture() + self._register_setup_function_fixture() + self.session._fixturemanager.parsefactories(self) + return super().collect() + + def _register_setup_module_fixture(self) -> None: + """Register an autouse, module-scoped fixture for the collected module object + that invokes setUpModule/tearDownModule if either or both are available. + + Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with + other fixtures (#517). + """ + setup_module = _get_first_non_fixture_func( + self.obj, ("setUpModule", "setup_module") + ) + teardown_module = _get_first_non_fixture_func( + self.obj, ("tearDownModule", "teardown_module") + ) + + if setup_module is None and teardown_module is None: + return + + def xunit_setup_module_fixture(request) -> Generator[None]: + module = request.module + if setup_module is not None: + _call_with_optional_argument(setup_module, module) + yield + if teardown_module is not None: + _call_with_optional_argument(teardown_module, module) + + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_module_fixture_{self.obj.__name__}", + func=xunit_setup_module_fixture, + nodeid=self.nodeid, + scope="module", + autouse=True, + ) + + def _register_setup_function_fixture(self) -> None: + """Register an autouse, function-scoped fixture for the collected module object + that invokes setup_function/teardown_function if either or both are available. + + Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with + other fixtures (#517). + """ + setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",)) + teardown_function = _get_first_non_fixture_func( + self.obj, ("teardown_function",) + ) + if setup_function is None and teardown_function is None: + return + + def xunit_setup_function_fixture(request) -> Generator[None]: + if request.instance is not None: + # in this case we are bound to an instance, so we need to let + # setup_method handle this + yield + return + function = request.function + if setup_function is not None: + _call_with_optional_argument(setup_function, function) + yield + if teardown_function is not None: + _call_with_optional_argument(teardown_function, function) + + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_function_fixture_{self.obj.__name__}", + func=xunit_setup_function_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) + + +class Package(nodes.Directory): + """Collector for files and directories in a Python packages -- directories + with an `__init__.py` file. + + .. note:: + + Directories without an `__init__.py` file are instead collected by + :class:`~pytest.Dir` by default. Both are :class:`~pytest.Directory` + collectors. + + .. versionchanged:: 8.0 + + Now inherits from :class:`~pytest.Directory`. + """ + + def __init__( + self, + fspath: LEGACY_PATH | None, + parent: nodes.Collector, + # NOTE: following args are unused: + config=None, + session=None, + nodeid=None, + path: Path | None = None, + ) -> None: + # NOTE: Could be just the following, but kept as-is for compat. + # super().__init__(self, fspath, parent=parent) + session = parent.session + super().__init__( + fspath=fspath, + path=path, + parent=parent, + config=config, + session=session, + nodeid=nodeid, + ) + + def setup(self) -> None: + init_mod = importtestmodule(self.path / "__init__.py", self.config) + + # Not using fixtures to call setup_module here because autouse fixtures + # from packages are not called automatically (#4085). + setup_module = _get_first_non_fixture_func( + init_mod, ("setUpModule", "setup_module") + ) + if setup_module is not None: + _call_with_optional_argument(setup_module, init_mod) + + teardown_module = _get_first_non_fixture_func( + init_mod, ("tearDownModule", "teardown_module") + ) + if teardown_module is not None: + func = partial(_call_with_optional_argument, teardown_module, init_mod) + self.addfinalizer(func) + + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: + # Always collect __init__.py first. + def sort_key(entry: os.DirEntry[str]) -> object: + return (entry.name != "__init__.py", entry.name) + + config = self.config + col: nodes.Collector | None + cols: Sequence[nodes.Collector] + ihook = self.ihook + for direntry in scandir(self.path, sort_key): + if direntry.is_dir(): + path = Path(direntry.path) + if not self.session.isinitpath(path, with_parents=True): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + col = ihook.pytest_collect_directory(path=path, parent=self) + if col is not None: + yield col + + elif direntry.is_file(): + path = Path(direntry.path) + if not self.session.isinitpath(path): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + cols = ihook.pytest_collect_file(file_path=path, parent=self) + yield from cols + + +def _call_with_optional_argument(func, arg) -> None: + """Call the given function with the given argument if func accepts one argument, otherwise + calls func without arguments.""" + arg_count = func.__code__.co_argcount + if inspect.ismethod(func): + arg_count -= 1 + if arg_count: + func(arg) + else: + func() + + +def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None: + """Return the attribute from the given object to be used as a setup/teardown + xunit-style function, but only if not marked as a fixture to avoid calling it twice. + """ + for name in names: + meth: object | None = getattr(obj, name, None) + if meth is not None and fixtures.getfixturemarker(meth) is None: + return meth + return None + + +class Class(PyCollector): + """Collector for test methods (and nested classes) in a Python class.""" + + @classmethod + def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override] + """The public constructor.""" + return super().from_parent(name=name, parent=parent, **kw) + + def newinstance(self): + return self.obj() + + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: + if not safe_getattr(self.obj, "__test__", True): + return [] + if hasinit(self.obj): + assert self.parent is not None + self.warn( + PytestCollectionWarning( + f"cannot collect test class {self.obj.__name__!r} because it has a " + f"__init__ constructor (from: {self.parent.nodeid})" + ) + ) + return [] + elif hasnew(self.obj): + assert self.parent is not None + self.warn( + PytestCollectionWarning( + f"cannot collect test class {self.obj.__name__!r} because it has a " + f"__new__ constructor (from: {self.parent.nodeid})" + ) + ) + return [] + + self._register_setup_class_fixture() + self._register_setup_method_fixture() + + self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) + + return super().collect() + + def _register_setup_class_fixture(self) -> None: + """Register an autouse, class scoped fixture into the collected class object + that invokes setup_class/teardown_class if either or both are available. + + Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with + other fixtures (#517). + """ + setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",)) + teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",)) + if setup_class is None and teardown_class is None: + return + + def xunit_setup_class_fixture(request) -> Generator[None]: + cls = request.cls + if setup_class is not None: + func = getimfunc(setup_class) + _call_with_optional_argument(func, cls) + yield + if teardown_class is not None: + func = getimfunc(teardown_class) + _call_with_optional_argument(func, cls) + + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", + func=xunit_setup_class_fixture, + nodeid=self.nodeid, + scope="class", + autouse=True, + ) + + def _register_setup_method_fixture(self) -> None: + """Register an autouse, function scoped fixture into the collected class object + that invokes setup_method/teardown_method if either or both are available. + + Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with + other fixtures (#517). + """ + setup_name = "setup_method" + setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) + teardown_name = "teardown_method" + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) + if setup_method is None and teardown_method is None: + return + + def xunit_setup_method_fixture(request) -> Generator[None]: + instance = request.instance + method = request.function + if setup_method is not None: + func = getattr(instance, setup_name) + _call_with_optional_argument(func, method) + yield + if teardown_method is not None: + func = getattr(instance, teardown_name) + _call_with_optional_argument(func, method) + + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", + func=xunit_setup_method_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) + + +def hasinit(obj: object) -> bool: + init: object = getattr(obj, "__init__", None) + if init: + return init != object.__init__ + return False + + +def hasnew(obj: object) -> bool: + new: object = getattr(obj, "__new__", None) + if new: + return new != object.__new__ + return False + + +@final +@dataclasses.dataclass(frozen=True) +class IdMaker: + """Make IDs for a parametrization.""" + + __slots__ = ( + "argnames", + "parametersets", + "idfn", + "ids", + "config", + "nodeid", + "func_name", + ) + + # The argnames of the parametrization. + argnames: Sequence[str] + # The ParameterSets of the parametrization. + parametersets: Sequence[ParameterSet] + # Optionally, a user-provided callable to make IDs for parameters in a + # ParameterSet. + idfn: Callable[[Any], object | None] | None + # Optionally, explicit IDs for ParameterSets by index. + ids: Sequence[object | None] | None + # Optionally, the pytest config. + # Used for controlling ASCII escaping, and for calling the + # :hook:`pytest_make_parametrize_id` hook. + config: Config | None + # Optionally, the ID of the node being parametrized. + # Used only for clearer error messages. + nodeid: str | None + # Optionally, the ID of the function being parametrized. + # Used only for clearer error messages. + func_name: str | None + + def make_unique_parameterset_ids(self) -> list[str]: + """Make a unique identifier for each ParameterSet, that may be used to + identify the parametrization in a node ID. + + Format is -...-[counter], where prm_x_token is + - user-provided id, if given + - else an id derived from the value, applicable for certain types + - else + The counter suffix is appended only in case a string wouldn't be unique + otherwise. + """ + resolved_ids = list(self._resolve_ids()) + # All IDs must be unique! + if len(resolved_ids) != len(set(resolved_ids)): + # Record the number of occurrences of each ID. + id_counts = Counter(resolved_ids) + # Map the ID to its next suffix. + id_suffixes: dict[str, int] = defaultdict(int) + # Suffix non-unique IDs to make them unique. + for index, id in enumerate(resolved_ids): + if id_counts[id] > 1: + suffix = "" + if id and id[-1].isdigit(): + suffix = "_" + new_id = f"{id}{suffix}{id_suffixes[id]}" + while new_id in set(resolved_ids): + id_suffixes[id] += 1 + new_id = f"{id}{suffix}{id_suffixes[id]}" + resolved_ids[index] = new_id + id_suffixes[id] += 1 + assert len(resolved_ids) == len( + set(resolved_ids) + ), f"Internal error: {resolved_ids=}" + return resolved_ids + + def _resolve_ids(self) -> Iterable[str]: + """Resolve IDs for all ParameterSets (may contain duplicates).""" + for idx, parameterset in enumerate(self.parametersets): + if parameterset.id is not None: + # ID provided directly - pytest.param(..., id="...") + yield parameterset.id + elif self.ids and idx < len(self.ids) and self.ids[idx] is not None: + # ID provided in the IDs list - parametrize(..., ids=[...]). + yield self._idval_from_value_required(self.ids[idx], idx) + else: + # ID not provided - generate it. + yield "-".join( + self._idval(val, argname, idx) + for val, argname in zip(parameterset.values, self.argnames) + ) + + def _idval(self, val: object, argname: str, idx: int) -> str: + """Make an ID for a parameter in a ParameterSet.""" + idval = self._idval_from_function(val, argname, idx) + if idval is not None: + return idval + idval = self._idval_from_hook(val, argname) + if idval is not None: + return idval + idval = self._idval_from_value(val) + if idval is not None: + return idval + return self._idval_from_argname(argname, idx) + + def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None: + """Try to make an ID for a parameter in a ParameterSet using the + user-provided id callable, if given.""" + if self.idfn is None: + return None + try: + id = self.idfn(val) + except Exception as e: + prefix = f"{self.nodeid}: " if self.nodeid is not None else "" + msg = "error raised while trying to determine id of parameter '{}' at position {}" + msg = prefix + msg.format(argname, idx) + raise ValueError(msg) from e + if id is None: + return None + return self._idval_from_value(id) + + def _idval_from_hook(self, val: object, argname: str) -> str | None: + """Try to make an ID for a parameter in a ParameterSet by calling the + :hook:`pytest_make_parametrize_id` hook.""" + if self.config: + id: str | None = self.config.hook.pytest_make_parametrize_id( + config=self.config, val=val, argname=argname + ) + return id + return None + + def _idval_from_value(self, val: object) -> str | None: + """Try to make an ID for a parameter in a ParameterSet from its value, + if the value type is supported.""" + if isinstance(val, (str, bytes)): + return _ascii_escaped_by_config(val, self.config) + elif val is None or isinstance(val, (float, int, bool, complex)): + return str(val) + elif isinstance(val, Pattern): + return ascii_escaped(val.pattern) + elif val is NOTSET: + # Fallback to default. Note that NOTSET is an enum.Enum. + pass + elif isinstance(val, enum.Enum): + return str(val) + elif isinstance(getattr(val, "__name__", None), str): + # Name of a class, function, module, etc. + name: str = getattr(val, "__name__") + return name + return None + + def _idval_from_value_required(self, val: object, idx: int) -> str: + """Like _idval_from_value(), but fails if the type is not supported.""" + id = self._idval_from_value(val) + if id is not None: + return id + + # Fail. + if self.func_name is not None: + prefix = f"In {self.func_name}: " + elif self.nodeid is not None: + prefix = f"In {self.nodeid}: " + else: + prefix = "" + msg = ( + f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. " + "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__." + ) + fail(msg, pytrace=False) + + @staticmethod + def _idval_from_argname(argname: str, idx: int) -> str: + """Make an ID for a parameter in a ParameterSet from the argument name + and the index of the ParameterSet.""" + return str(argname) + str(idx) + + +@final +@dataclasses.dataclass(frozen=True) +class CallSpec2: + """A planned parameterized invocation of a test function. + + Calculated during collection for a given test function's Metafunc. + Once collection is over, each callspec is turned into a single Item + and stored in item.callspec. + """ + + # arg name -> arg value which will be passed to a fixture or pseudo-fixture + # of the same name. (indirect or direct parametrization respectively) + params: dict[str, object] = dataclasses.field(default_factory=dict) + # arg name -> arg index. + indices: dict[str, int] = dataclasses.field(default_factory=dict) + # Used for sorting parametrized resources. + _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict) + # Parts which will be added to the item's name in `[..]` separated by "-". + _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) + # Marks which will be applied to the item. + marks: list[Mark] = dataclasses.field(default_factory=list) + + def setmulti( + self, + *, + argnames: Iterable[str], + valset: Iterable[object], + id: str, + marks: Iterable[Mark | MarkDecorator], + scope: Scope, + param_index: int, + ) -> CallSpec2: + params = self.params.copy() + indices = self.indices.copy() + arg2scope = dict(self._arg2scope) + for arg, val in zip(argnames, valset): + if arg in params: + raise ValueError(f"duplicate parametrization of {arg!r}") + params[arg] = val + indices[arg] = param_index + arg2scope[arg] = scope + return CallSpec2( + params=params, + indices=indices, + _arg2scope=arg2scope, + _idlist=[*self._idlist, id], + marks=[*self.marks, *normalize_mark_list(marks)], + ) + + def getparam(self, name: str) -> object: + try: + return self.params[name] + except KeyError as e: + raise ValueError(name) from e + + @property + def id(self) -> str: + return "-".join(self._idlist) + + +def get_direct_param_fixture_func(request: FixtureRequest) -> Any: + return request.param + + +# Used for storing pseudo fixturedefs for direct parametrization. +name2pseudofixturedef_key = StashKey[Dict[str, FixtureDef[Any]]]() + + +@final +class Metafunc: + """Objects passed to the :hook:`pytest_generate_tests` hook. + + They help to inspect a test function and to generate tests according to + test configuration or values specified in the class or module where a + test function is defined. + """ + + def __init__( + self, + definition: FunctionDefinition, + fixtureinfo: fixtures.FuncFixtureInfo, + config: Config, + cls=None, + module=None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + + #: Access to the underlying :class:`_pytest.python.FunctionDefinition`. + self.definition = definition + + #: Access to the :class:`pytest.Config` object for the test session. + self.config = config + + #: The module object where the test function is defined in. + self.module = module + + #: Underlying Python test function. + self.function = definition.obj + + #: Set of fixture names required by the test function. + self.fixturenames = fixtureinfo.names_closure + + #: Class object where the test function is defined in or ``None``. + self.cls = cls + + self._arg2fixturedefs = fixtureinfo.name2fixturedefs + + # Result of parametrize(). + self._calls: list[CallSpec2] = [] + + def parametrize( + self, + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + indirect: bool | Sequence[str] = False, + ids: Iterable[object | None] | Callable[[Any], object | None] | None = None, + scope: _ScopeName | None = None, + *, + _param_mark: Mark | None = None, + ) -> None: + """Add new invocations to the underlying test function using the list + of argvalues for the given argnames. Parametrization is performed + during the collection phase. If you need to setup expensive resources + see about setting indirect to do it rather than at test setup time. + + Can be called multiple times per test function (but only on different + argument names), in which case each call parametrizes all previous + parametrizations, e.g. + + :: + + unparametrized: t + parametrize ["x", "y"]: t[x], t[y] + parametrize [1, 2]: t[x-1], t[x-2], t[y-1], t[y-2] + + :param argnames: + A comma-separated string denoting one or more argument names, or + a list/tuple of argument strings. + + :param argvalues: + The list of argvalues determines how often a test is invoked with + different argument values. + + If only one argname was specified argvalues is a list of values. + If N argnames were specified, argvalues must be a list of + N-tuples, where each tuple-element specifies a value for its + respective argname. + :type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object] + :param indirect: + A list of arguments' names (subset of argnames) or a boolean. + If True the list contains all names from the argnames. Each + argvalue corresponding to an argname in this list will + be passed as request.param to its respective argname fixture + function so that it can perform more expensive setups during the + setup phase of a test rather than at collection time. + + :param ids: + Sequence of (or generator for) ids for ``argvalues``, + or a callable to return part of the id for each argvalue. + + With sequences (and generators like ``itertools.count()``) the + returned ids should be of type ``string``, ``int``, ``float``, + ``bool``, or ``None``. + They are mapped to the corresponding index in ``argvalues``. + ``None`` means to use the auto-generated id. + + If it is a callable it will be called for each entry in + ``argvalues``, and the return value is used as part of the + auto-generated id for the whole set (where parts are joined with + dashes ("-")). + This is useful to provide more specific ids for certain items, e.g. + dates. Returning ``None`` will use an auto-generated id. + + If no ids are provided they will be generated automatically from + the argvalues. + + :param scope: + If specified it denotes the scope of the parameters. + The scope is used for grouping tests by parameter instances. + It will also override any fixture-function defined scope, allowing + to set a dynamic scope using test context or configuration. + """ + argnames, parametersets = ParameterSet._for_parametrize( + argnames, + argvalues, + self.function, + self.config, + nodeid=self.definition.nodeid, + ) + del argvalues + + if "request" in argnames: + fail( + "'request' is a reserved name and cannot be used in @pytest.mark.parametrize", + pytrace=False, + ) + + if scope is not None: + scope_ = Scope.from_user( + scope, descr=f"parametrize() call in {self.function.__name__}" + ) + else: + scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) + + self._validate_if_using_arg_names(argnames, indirect) + + # Use any already (possibly) generated ids with parametrize Marks. + if _param_mark and _param_mark._param_ids_from: + generated_ids = _param_mark._param_ids_from._param_ids_generated + if generated_ids is not None: + ids = generated_ids + + ids = self._resolve_parameter_set_ids( + argnames, ids, parametersets, nodeid=self.definition.nodeid + ) + + # Store used (possibly generated) ids with parametrize Marks. + if _param_mark and _param_mark._param_ids_from and generated_ids is None: + object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids) + + # Add funcargs as fixturedefs to fixtureinfo.arg2fixturedefs by registering + # artificial "pseudo" FixtureDef's so that later at test execution time we can + # rely on a proper FixtureDef to exist for fixture setup. + node = None + # If we have a scope that is higher than function, we need + # to make sure we only ever create an according fixturedef on + # a per-scope basis. We thus store and cache the fixturedef on the + # node related to the scope. + if scope_ is not Scope.Function: + collector = self.definition.parent + assert collector is not None + node = get_scope_node(collector, scope_) + if node is None: + # If used class scope and there is no class, use module-level + # collector (for now). + if scope_ is Scope.Class: + assert isinstance(collector, Module) + node = collector + # If used package scope and there is no package, use session + # (for now). + elif scope_ is Scope.Package: + node = collector.session + else: + assert False, f"Unhandled missing scope: {scope}" + if node is None: + name2pseudofixturedef = None + else: + default: dict[str, FixtureDef[Any]] = {} + name2pseudofixturedef = node.stash.setdefault( + name2pseudofixturedef_key, default + ) + arg_directness = self._resolve_args_directness(argnames, indirect) + for argname in argnames: + if arg_directness[argname] == "indirect": + continue + if name2pseudofixturedef is not None and argname in name2pseudofixturedef: + fixturedef = name2pseudofixturedef[argname] + else: + fixturedef = FixtureDef( + config=self.config, + baseid="", + argname=argname, + func=get_direct_param_fixture_func, + scope=scope_, + params=None, + ids=None, + _ispytest=True, + ) + if name2pseudofixturedef is not None: + name2pseudofixturedef[argname] = fixturedef + self._arg2fixturedefs[argname] = [fixturedef] + + # Create the new calls: if we are parametrize() multiple times (by applying the decorator + # more than once) then we accumulate those calls generating the cartesian product + # of all calls. + newcalls = [] + for callspec in self._calls or [CallSpec2()]: + for param_index, (param_id, param_set) in enumerate( + zip(ids, parametersets) + ): + newcallspec = callspec.setmulti( + argnames=argnames, + valset=param_set.values, + id=param_id, + marks=param_set.marks, + scope=scope_, + param_index=param_index, + ) + newcalls.append(newcallspec) + self._calls = newcalls + + def _resolve_parameter_set_ids( + self, + argnames: Sequence[str], + ids: Iterable[object | None] | Callable[[Any], object | None] | None, + parametersets: Sequence[ParameterSet], + nodeid: str, + ) -> list[str]: + """Resolve the actual ids for the given parameter sets. + + :param argnames: + Argument names passed to ``parametrize()``. + :param ids: + The `ids` parameter of the ``parametrize()`` call (see docs). + :param parametersets: + The parameter sets, each containing a set of values corresponding + to ``argnames``. + :param nodeid str: + The nodeid of the definition item that generated this + parametrization. + :returns: + List with ids for each parameter set given. + """ + if ids is None: + idfn = None + ids_ = None + elif callable(ids): + idfn = ids + ids_ = None + else: + idfn = None + ids_ = self._validate_ids(ids, parametersets, self.function.__name__) + id_maker = IdMaker( + argnames, + parametersets, + idfn, + ids_, + self.config, + nodeid=nodeid, + func_name=self.function.__name__, + ) + return id_maker.make_unique_parameterset_ids() + + def _validate_ids( + self, + ids: Iterable[object | None], + parametersets: Sequence[ParameterSet], + func_name: str, + ) -> list[object | None]: + try: + num_ids = len(ids) # type: ignore[arg-type] + except TypeError: + try: + iter(ids) + except TypeError as e: + raise TypeError("ids must be a callable or an iterable") from e + num_ids = len(parametersets) + + # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849 + if num_ids != len(parametersets) and num_ids != 0: + msg = "In {}: {} parameter sets specified, with different number of ids: {}" + fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False) + + return list(itertools.islice(ids, num_ids)) + + def _resolve_args_directness( + self, + argnames: Sequence[str], + indirect: bool | Sequence[str], + ) -> dict[str, Literal["indirect", "direct"]]: + """Resolve if each parametrized argument must be considered an indirect + parameter to a fixture of the same name, or a direct parameter to the + parametrized function, based on the ``indirect`` parameter of the + parametrized() call. + + :param argnames: + List of argument names passed to ``parametrize()``. + :param indirect: + Same as the ``indirect`` parameter of ``parametrize()``. + :returns + A dict mapping each arg name to either "indirect" or "direct". + """ + arg_directness: dict[str, Literal["indirect", "direct"]] + if isinstance(indirect, bool): + arg_directness = dict.fromkeys( + argnames, "indirect" if indirect else "direct" + ) + elif isinstance(indirect, Sequence): + arg_directness = dict.fromkeys(argnames, "direct") + for arg in indirect: + if arg not in argnames: + fail( + f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist", + pytrace=False, + ) + arg_directness[arg] = "indirect" + else: + fail( + f"In {self.function.__name__}: expected Sequence or boolean" + f" for indirect, got {type(indirect).__name__}", + pytrace=False, + ) + return arg_directness + + def _validate_if_using_arg_names( + self, + argnames: Sequence[str], + indirect: bool | Sequence[str], + ) -> None: + """Check if all argnames are being used, by default values, or directly/indirectly. + + :param List[str] argnames: List of argument names passed to ``parametrize()``. + :param indirect: Same as the ``indirect`` parameter of ``parametrize()``. + :raises ValueError: If validation fails. + """ + default_arg_names = set(get_default_arg_names(self.function)) + func_name = self.function.__name__ + for arg in argnames: + if arg not in self.fixturenames: + if arg in default_arg_names: + fail( + f"In {func_name}: function already takes an argument '{arg}' with a default value", + pytrace=False, + ) + else: + if isinstance(indirect, Sequence): + name = "fixture" if arg in indirect else "argument" + else: + name = "fixture" if indirect else "argument" + fail( + f"In {func_name}: function uses no {name} '{arg}'", + pytrace=False, + ) + + +def _find_parametrized_scope( + argnames: Sequence[str], + arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]], + indirect: bool | Sequence[str], +) -> Scope: + """Find the most appropriate scope for a parametrized call based on its arguments. + + When there's at least one direct argument, always use "function" scope. + + When a test function is parametrized and all its arguments are indirect + (e.g. fixtures), return the most narrow scope based on the fixtures used. + + Related to issue #1832, based on code posted by @Kingdread. + """ + if isinstance(indirect, Sequence): + all_arguments_are_fixtures = len(indirect) == len(argnames) + else: + all_arguments_are_fixtures = bool(indirect) + + if all_arguments_are_fixtures: + fixturedefs = arg2fixturedefs or {} + used_scopes = [ + fixturedef[-1]._scope + for name, fixturedef in fixturedefs.items() + if name in argnames + ] + # Takes the most narrow scope from used fixtures. + return min(used_scopes, default=Scope.Function) + + return Scope.Function + + +def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str: + if config is None: + escape_option = False + else: + escape_option = config.getini( + "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" + ) + # TODO: If escaping is turned off and the user passes bytes, + # will return a bytes. For now we ignore this but the + # code *probably* doesn't handle this case. + return val if escape_option else ascii_escaped(val) # type: ignore + + +class Function(PyobjMixin, nodes.Item): + """Item responsible for setting up and executing a Python test function. + + :param name: + The full function name, including any decorations like those + added by parametrization (``my_func[my_param]``). + :param parent: + The parent Node. + :param config: + The pytest Config object. + :param callspec: + If given, this function has been parametrized and the callspec contains + meta information about the parametrization. + :param callobj: + If given, the object which will be called when the Function is invoked, + otherwise the callobj will be obtained from ``parent`` using ``originalname``. + :param keywords: + Keywords bound to the function object for "-k" matching. + :param session: + The pytest Session object. + :param fixtureinfo: + Fixture information already resolved at this fixture node.. + :param originalname: + The attribute name to use for accessing the underlying function object. + Defaults to ``name``. Set this if name is different from the original name, + for example when it contains decorations like those added by parametrization + (``my_func[my_param]``). + """ + + # Disable since functions handle it themselves. + _ALLOW_MARKERS = False + + def __init__( + self, + name: str, + parent, + config: Config | None = None, + callspec: CallSpec2 | None = None, + callobj=NOTSET, + keywords: Mapping[str, Any] | None = None, + session: Session | None = None, + fixtureinfo: FuncFixtureInfo | None = None, + originalname: str | None = None, + ) -> None: + super().__init__(name, parent, config=config, session=session) + + if callobj is not NOTSET: + self._obj = callobj + self._instance = getattr(callobj, "__self__", None) + + #: Original function name, without any decorations (for example + #: parametrization adds a ``"[...]"`` suffix to function names), used to access + #: the underlying function object from ``parent`` (in case ``callobj`` is not given + #: explicitly). + #: + #: .. versionadded:: 3.0 + self.originalname = originalname or name + + # Note: when FunctionDefinition is introduced, we should change ``originalname`` + # to a readonly property that returns FunctionDefinition.name. + + self.own_markers.extend(get_unpacked_marks(self.obj)) + if callspec: + self.callspec = callspec + self.own_markers.extend(callspec.marks) + + # todo: this is a hell of a hack + # https://github.com/pytest-dev/pytest/issues/4569 + # Note: the order of the updates is important here; indicates what + # takes priority (ctor argument over function attributes over markers). + # Take own_markers only; NodeKeywords handles parent traversal on its own. + self.keywords.update((mark.name, mark) for mark in self.own_markers) + self.keywords.update(self.obj.__dict__) + if keywords: + self.keywords.update(keywords) + + if fixtureinfo is None: + fm = self.session._fixturemanager + fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls) + self._fixtureinfo: FuncFixtureInfo = fixtureinfo + self.fixturenames = fixtureinfo.names_closure + self._initrequest() + + # todo: determine sound type limitations + @classmethod + def from_parent(cls, parent, **kw) -> Self: + """The public constructor.""" + return super().from_parent(parent=parent, **kw) + + def _initrequest(self) -> None: + self.funcargs: dict[str, object] = {} + self._request = fixtures.TopRequest(self, _ispytest=True) + + @property + def function(self): + """Underlying python 'function' object.""" + return getimfunc(self.obj) + + @property + def instance(self): + try: + return self._instance + except AttributeError: + if isinstance(self.parent, Class): + # Each Function gets a fresh class instance. + self._instance = self._getinstance() + else: + self._instance = None + return self._instance + + def _getinstance(self): + if isinstance(self.parent, Class): + # Each Function gets a fresh class instance. + return self.parent.newinstance() + else: + return None + + def _getobj(self): + instance = self.instance + if instance is not None: + parent_obj = instance + else: + assert self.parent is not None + parent_obj = self.parent.obj # type: ignore[attr-defined] + return getattr(parent_obj, self.originalname) + + @property + def _pyfuncitem(self): + """(compatonly) for code expecting pytest-2.2 style request objects.""" + return self + + def runtest(self) -> None: + """Execute the underlying test function.""" + self.ihook.pytest_pyfunc_call(pyfuncitem=self) + + def setup(self) -> None: + self._request._fillfixtures() + + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: + if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False): + code = _pytest._code.Code.from_function(get_real_func(self.obj)) + path, firstlineno = code.path, code.firstlineno + traceback = excinfo.traceback + ntraceback = traceback.cut(path=path, firstlineno=firstlineno) + if ntraceback == traceback: + ntraceback = ntraceback.cut(path=path) + if ntraceback == traceback: + ntraceback = ntraceback.filter(filter_traceback) + if not ntraceback: + ntraceback = traceback + ntraceback = ntraceback.filter(excinfo) + + # issue364: mark all but first and last frames to + # only show a single-line message for each frame. + if self.config.getoption("tbstyle", "auto") == "auto": + if len(ntraceback) > 2: + ntraceback = Traceback( + ( + ntraceback[0], + *(t.with_repr_style("short") for t in ntraceback[1:-1]), + ntraceback[-1], + ) + ) + + return ntraceback + return excinfo.traceback + + # TODO: Type ignored -- breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, + excinfo: ExceptionInfo[BaseException], + ) -> str | TerminalRepr: + style = self.config.getoption("tbstyle", "auto") + if style == "auto": + style = "long" + return self._repr_failure_py(excinfo, style=style) + + +class FunctionDefinition(Function): + """This class is a stop gap solution until we evolve to have actual function + definition nodes and manage to get rid of ``metafunc``.""" + + def runtest(self) -> None: + raise RuntimeError("function definitions are not supposed to be run as tests") + + setup = runtest diff --git a/.venv/Lib/site-packages/_pytest/python_api.py b/.venv/Lib/site-packages/_pytest/python_api.py new file mode 100644 index 0000000..f0035f0 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/python_api.py @@ -0,0 +1,1028 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +from collections.abc import Collection +from collections.abc import Sized +from decimal import Decimal +import math +from numbers import Complex +import pprint +import re +from types import TracebackType +from typing import Any +from typing import Callable +from typing import cast +from typing import ContextManager +from typing import final +from typing import Mapping +from typing import overload +from typing import Pattern +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar + +import _pytest._code +from _pytest.outcomes import fail + + +if TYPE_CHECKING: + from numpy import ndarray + + +def _compare_approx( + full_object: object, + message_data: Sequence[tuple[str, str, str]], + number_of_elements: int, + different_ids: Sequence[object], + max_abs_diff: float, + max_rel_diff: float, +) -> list[str]: + message_list = list(message_data) + message_list.insert(0, ("Index", "Obtained", "Expected")) + max_sizes = [0, 0, 0] + for index, obtained, expected in message_list: + max_sizes[0] = max(max_sizes[0], len(index)) + max_sizes[1] = max(max_sizes[1], len(obtained)) + max_sizes[2] = max(max_sizes[2], len(expected)) + explanation = [ + f"comparison failed. Mismatched elements: {len(different_ids)} / {number_of_elements}:", + f"Max absolute difference: {max_abs_diff}", + f"Max relative difference: {max_rel_diff}", + ] + [ + f"{indexes:<{max_sizes[0]}} | {obtained:<{max_sizes[1]}} | {expected:<{max_sizes[2]}}" + for indexes, obtained, expected in message_list + ] + return explanation + + +# builtin pytest.approx helper + + +class ApproxBase: + """Provide shared utilities for making approximate comparisons between + numbers or sequences of numbers.""" + + # Tell numpy to use our `__eq__` operator instead of its. + __array_ufunc__ = None + __array_priority__ = 100 + + def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: + __tracebackhide__ = True + self.expected = expected + self.abs = abs + self.rel = rel + self.nan_ok = nan_ok + self._check_type() + + def __repr__(self) -> str: + raise NotImplementedError + + def _repr_compare(self, other_side: Any) -> list[str]: + return [ + "comparison failed", + f"Obtained: {other_side}", + f"Expected: {self}", + ] + + def __eq__(self, actual) -> bool: + return all( + a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual) + ) + + def __bool__(self): + __tracebackhide__ = True + raise AssertionError( + "approx() is not supported in a boolean context.\nDid you mean: `assert a == approx(b)`?" + ) + + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore + + def __ne__(self, actual) -> bool: + return not (actual == self) + + def _approx_scalar(self, x) -> ApproxScalar: + if isinstance(x, Decimal): + return ApproxDecimal(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) + return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) + + def _yield_comparisons(self, actual): + """Yield all the pairs of numbers to be compared. + + This is used to implement the `__eq__` method. + """ + raise NotImplementedError + + def _check_type(self) -> None: + """Raise a TypeError if the expected value is not a valid type.""" + # This is only a concern if the expected value is a sequence. In every + # other case, the approx() function ensures that the expected value has + # a numeric type. For this reason, the default is to do nothing. The + # classes that deal with sequences should reimplement this method to + # raise if there are any non-numeric elements in the sequence. + + +def _recursive_sequence_map(f, x): + """Recursively map a function over a sequence of arbitrary depth""" + if isinstance(x, (list, tuple)): + seq_type = type(x) + return seq_type(_recursive_sequence_map(f, xi) for xi in x) + elif _is_sequence_like(x): + return [_recursive_sequence_map(f, xi) for xi in x] + else: + return f(x) + + +class ApproxNumpy(ApproxBase): + """Perform approximate comparisons where the expected value is numpy array.""" + + def __repr__(self) -> str: + list_scalars = _recursive_sequence_map( + self._approx_scalar, self.expected.tolist() + ) + return f"approx({list_scalars!r})" + + def _repr_compare(self, other_side: ndarray | list[Any]) -> list[str]: + import itertools + import math + + def get_value_from_nested_list( + nested_list: list[Any], nd_index: tuple[Any, ...] + ) -> Any: + """ + Helper function to get the value out of a nested list, given an n-dimensional index. + This mimics numpy's indexing, but for raw nested python lists. + """ + value: Any = nested_list + for i in nd_index: + value = value[i] + return value + + np_array_shape = self.expected.shape + approx_side_as_seq = _recursive_sequence_map( + self._approx_scalar, self.expected.tolist() + ) + + # convert other_side to numpy array to ensure shape attribute is available + other_side_as_array = _as_numpy_array(other_side) + assert other_side_as_array is not None + + if np_array_shape != other_side_as_array.shape: + return [ + "Impossible to compare arrays with different shapes.", + f"Shapes: {np_array_shape} and {other_side_as_array.shape}", + ] + + number_of_elements = self.expected.size + max_abs_diff = -math.inf + max_rel_diff = -math.inf + different_ids = [] + for index in itertools.product(*(range(i) for i in np_array_shape)): + approx_value = get_value_from_nested_list(approx_side_as_seq, index) + other_value = get_value_from_nested_list(other_side_as_array, index) + if approx_value != other_value: + abs_diff = abs(approx_value.expected - other_value) + max_abs_diff = max(max_abs_diff, abs_diff) + if other_value == 0.0: + max_rel_diff = math.inf + else: + max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) + different_ids.append(index) + + message_data = [ + ( + str(index), + str(get_value_from_nested_list(other_side_as_array, index)), + str(get_value_from_nested_list(approx_side_as_seq, index)), + ) + for index in different_ids + ] + return _compare_approx( + self.expected, + message_data, + number_of_elements, + different_ids, + max_abs_diff, + max_rel_diff, + ) + + def __eq__(self, actual) -> bool: + import numpy as np + + # self.expected is supposed to always be an array here. + + if not np.isscalar(actual): + try: + actual = np.asarray(actual) + except Exception as e: + raise TypeError(f"cannot compare '{actual}' to numpy.ndarray") from e + + if not np.isscalar(actual) and actual.shape != self.expected.shape: + return False + + return super().__eq__(actual) + + def _yield_comparisons(self, actual): + import numpy as np + + # `actual` can either be a numpy array or a scalar, it is treated in + # `__eq__` before being passed to `ApproxBase.__eq__`, which is the + # only method that calls this one. + + if np.isscalar(actual): + for i in np.ndindex(self.expected.shape): + yield actual, self.expected[i].item() + else: + for i in np.ndindex(self.expected.shape): + yield actual[i].item(), self.expected[i].item() + + +class ApproxMapping(ApproxBase): + """Perform approximate comparisons where the expected value is a mapping + with numeric values (the keys can be anything).""" + + def __repr__(self) -> str: + return f"approx({({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})" + + def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: + import math + + approx_side_as_map = { + k: self._approx_scalar(v) for k, v in self.expected.items() + } + + number_of_elements = len(approx_side_as_map) + max_abs_diff = -math.inf + max_rel_diff = -math.inf + different_ids = [] + for (approx_key, approx_value), other_value in zip( + approx_side_as_map.items(), other_side.values() + ): + if approx_value != other_value: + if approx_value.expected is not None and other_value is not None: + try: + max_abs_diff = max( + max_abs_diff, abs(approx_value.expected - other_value) + ) + if approx_value.expected == 0.0: + max_rel_diff = math.inf + else: + max_rel_diff = max( + max_rel_diff, + abs( + (approx_value.expected - other_value) + / approx_value.expected + ), + ) + except ZeroDivisionError: + pass + different_ids.append(approx_key) + + message_data = [ + (str(key), str(other_side[key]), str(approx_side_as_map[key])) + for key in different_ids + ] + + return _compare_approx( + self.expected, + message_data, + number_of_elements, + different_ids, + max_abs_diff, + max_rel_diff, + ) + + def __eq__(self, actual) -> bool: + try: + if set(actual.keys()) != set(self.expected.keys()): + return False + except AttributeError: + return False + + return super().__eq__(actual) + + def _yield_comparisons(self, actual): + for k in self.expected.keys(): + yield actual[k], self.expected[k] + + def _check_type(self) -> None: + __tracebackhide__ = True + for key, value in self.expected.items(): + if isinstance(value, type(self.expected)): + msg = "pytest.approx() does not support nested dictionaries: key={!r} value={!r}\n full mapping={}" + raise TypeError(msg.format(key, value, pprint.pformat(self.expected))) + + +class ApproxSequenceLike(ApproxBase): + """Perform approximate comparisons where the expected value is a sequence of numbers.""" + + def __repr__(self) -> str: + seq_type = type(self.expected) + if seq_type not in (tuple, list): + seq_type = list + return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})" + + def _repr_compare(self, other_side: Sequence[float]) -> list[str]: + import math + + if len(self.expected) != len(other_side): + return [ + "Impossible to compare lists with different sizes.", + f"Lengths: {len(self.expected)} and {len(other_side)}", + ] + + approx_side_as_map = _recursive_sequence_map(self._approx_scalar, self.expected) + + number_of_elements = len(approx_side_as_map) + max_abs_diff = -math.inf + max_rel_diff = -math.inf + different_ids = [] + for i, (approx_value, other_value) in enumerate( + zip(approx_side_as_map, other_side) + ): + if approx_value != other_value: + abs_diff = abs(approx_value.expected - other_value) + max_abs_diff = max(max_abs_diff, abs_diff) + if other_value == 0.0: + max_rel_diff = math.inf + else: + max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) + different_ids.append(i) + + message_data = [ + (str(i), str(other_side[i]), str(approx_side_as_map[i])) + for i in different_ids + ] + + return _compare_approx( + self.expected, + message_data, + number_of_elements, + different_ids, + max_abs_diff, + max_rel_diff, + ) + + def __eq__(self, actual) -> bool: + try: + if len(actual) != len(self.expected): + return False + except TypeError: + return False + return super().__eq__(actual) + + def _yield_comparisons(self, actual): + return zip(actual, self.expected) + + def _check_type(self) -> None: + __tracebackhide__ = True + for index, x in enumerate(self.expected): + if isinstance(x, type(self.expected)): + msg = "pytest.approx() does not support nested data structures: {!r} at index {}\n full sequence: {}" + raise TypeError(msg.format(x, index, pprint.pformat(self.expected))) + + +class ApproxScalar(ApproxBase): + """Perform approximate comparisons where the expected value is a single number.""" + + # Using Real should be better than this Union, but not possible yet: + # https://github.com/python/typeshed/pull/3108 + DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12 + DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6 + + def __repr__(self) -> str: + """Return a string communicating both the expected value and the + tolerance for the comparison being made. + + For example, ``1.0 ± 1e-6``, ``(3+4j) ± 5e-6 ∠ ±180°``. + """ + # Don't show a tolerance for values that aren't compared using + # tolerances, i.e. non-numerics and infinities. Need to call abs to + # handle complex numbers, e.g. (inf + 1j). + if ( + isinstance(self.expected, bool) + or (not isinstance(self.expected, (Complex, Decimal))) + or math.isinf(abs(self.expected) or isinstance(self.expected, bool)) + ): + return str(self.expected) + + # If a sensible tolerance can't be calculated, self.tolerance will + # raise a ValueError. In this case, display '???'. + try: + vetted_tolerance = f"{self.tolerance:.1e}" + if ( + isinstance(self.expected, Complex) + and self.expected.imag + and not math.isinf(self.tolerance) + ): + vetted_tolerance += " ∠ ±180°" + except ValueError: + vetted_tolerance = "???" + + return f"{self.expected} ± {vetted_tolerance}" + + def __eq__(self, actual) -> bool: + """Return whether the given value is equal to the expected value + within the pre-specified tolerance.""" + asarray = _as_numpy_array(actual) + if asarray is not None: + # Call ``__eq__()`` manually to prevent infinite-recursion with + # numpy<1.13. See #3748. + return all(self.__eq__(a) for a in asarray.flat) + + # Short-circuit exact equality, except for bool + if isinstance(self.expected, bool) and not isinstance(actual, bool): + return False + elif actual == self.expected: + return True + + # If either type is non-numeric, fall back to strict equality. + # NB: we need Complex, rather than just Number, to ensure that __abs__, + # __sub__, and __float__ are defined. Also, consider bool to be + # nonnumeric, even though it has the required arithmetic. + if isinstance(self.expected, bool) or not ( + isinstance(self.expected, (Complex, Decimal)) + and isinstance(actual, (Complex, Decimal)) + ): + return False + + # Allow the user to control whether NaNs are considered equal to each + # other or not. The abs() calls are for compatibility with complex + # numbers. + if math.isnan(abs(self.expected)): + return self.nan_ok and math.isnan(abs(actual)) + + # Infinity shouldn't be approximately equal to anything but itself, but + # if there's a relative tolerance, it will be infinite and infinity + # will seem approximately equal to everything. The equal-to-itself + # case would have been short circuited above, so here we can just + # return false if the expected value is infinite. The abs() call is + # for compatibility with complex numbers. + if math.isinf(abs(self.expected)): + return False + + # Return true if the two numbers are within the tolerance. + result: bool = abs(self.expected - actual) <= self.tolerance + return result + + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore + + @property + def tolerance(self): + """Return the tolerance for the comparison. + + This could be either an absolute tolerance or a relative tolerance, + depending on what the user specified or which would be larger. + """ + + def set_default(x, default): + return x if x is not None else default + + # Figure out what the absolute tolerance should be. ``self.abs`` is + # either None or a value specified by the user. + absolute_tolerance = set_default(self.abs, self.DEFAULT_ABSOLUTE_TOLERANCE) + + if absolute_tolerance < 0: + raise ValueError( + f"absolute tolerance can't be negative: {absolute_tolerance}" + ) + if math.isnan(absolute_tolerance): + raise ValueError("absolute tolerance can't be NaN.") + + # If the user specified an absolute tolerance but not a relative one, + # just return the absolute tolerance. + if self.rel is None: + if self.abs is not None: + return absolute_tolerance + + # Figure out what the relative tolerance should be. ``self.rel`` is + # either None or a value specified by the user. This is done after + # we've made sure the user didn't ask for an absolute tolerance only, + # because we don't want to raise errors about the relative tolerance if + # we aren't even going to use it. + relative_tolerance = set_default( + self.rel, self.DEFAULT_RELATIVE_TOLERANCE + ) * abs(self.expected) + + if relative_tolerance < 0: + raise ValueError( + f"relative tolerance can't be negative: {relative_tolerance}" + ) + if math.isnan(relative_tolerance): + raise ValueError("relative tolerance can't be NaN.") + + # Return the larger of the relative and absolute tolerances. + return max(relative_tolerance, absolute_tolerance) + + +class ApproxDecimal(ApproxScalar): + """Perform approximate comparisons where the expected value is a Decimal.""" + + DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12") + DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6") + + +def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: + """Assert that two numbers (or two ordered sequences of numbers) are equal to each other + within some tolerance. + + Due to the :doc:`python:tutorial/floatingpoint`, numbers that we + would intuitively expect to be equal are not always so:: + + >>> 0.1 + 0.2 == 0.3 + False + + This problem is commonly encountered when writing tests, e.g. when making + sure that floating-point values are what you expect them to be. One way to + deal with this problem is to assert that two floating-point numbers are + equal to within some appropriate tolerance:: + + >>> abs((0.1 + 0.2) - 0.3) < 1e-6 + True + + However, comparisons like this are tedious to write and difficult to + understand. Furthermore, absolute comparisons like the one above are + usually discouraged because there's no tolerance that works well for all + situations. ``1e-6`` is good for numbers around ``1``, but too small for + very big numbers and too big for very small ones. It's better to express + the tolerance as a fraction of the expected value, but relative comparisons + like that are even more difficult to write correctly and concisely. + + The ``approx`` class performs floating-point comparisons using a syntax + that's as intuitive as possible:: + + >>> from pytest import approx + >>> 0.1 + 0.2 == approx(0.3) + True + + The same syntax also works for ordered sequences of numbers:: + + >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6)) + True + + ``numpy`` arrays:: + + >>> import numpy as np # doctest: +SKIP + >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP + True + + And for a ``numpy`` array against a scalar:: + + >>> import numpy as np # doctest: +SKIP + >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP + True + + Only ordered sequences are supported, because ``approx`` needs + to infer the relative position of the sequences without ambiguity. This means + ``sets`` and other unordered sequences are not supported. + + Finally, dictionary *values* can also be compared:: + + >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6}) + True + + The comparison will be true if both mappings have the same keys and their + respective values match the expected tolerances. + + **Tolerances** + + By default, ``approx`` considers numbers within a relative tolerance of + ``1e-6`` (i.e. one part in a million) of its expected value to be equal. + This treatment would lead to surprising results if the expected value was + ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``. + To handle this case less surprisingly, ``approx`` also considers numbers + within an absolute tolerance of ``1e-12`` of its expected value to be + equal. Infinity and NaN are special cases. Infinity is only considered + equal to itself, regardless of the relative tolerance. NaN is not + considered equal to anything by default, but you can make it be equal to + itself by setting the ``nan_ok`` argument to True. (This is meant to + facilitate comparing arrays that use NaN to mean "no data".) + + Both the relative and absolute tolerances can be changed by passing + arguments to the ``approx`` constructor:: + + >>> 1.0001 == approx(1) + False + >>> 1.0001 == approx(1, rel=1e-3) + True + >>> 1.0001 == approx(1, abs=1e-3) + True + + If you specify ``abs`` but not ``rel``, the comparison will not consider + the relative tolerance at all. In other words, two numbers that are within + the default relative tolerance of ``1e-6`` will still be considered unequal + if they exceed the specified absolute tolerance. If you specify both + ``abs`` and ``rel``, the numbers will be considered equal if either + tolerance is met:: + + >>> 1 + 1e-8 == approx(1) + True + >>> 1 + 1e-8 == approx(1, abs=1e-12) + False + >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12) + True + + You can also use ``approx`` to compare nonnumeric types, or dicts and + sequences containing nonnumeric types, in which case it falls back to + strict equality. This can be useful for comparing dicts and sequences that + can contain optional values:: + + >>> {"required": 1.0000005, "optional": None} == approx({"required": 1, "optional": None}) + True + >>> [None, 1.0000005] == approx([None,1]) + True + >>> ["foo", 1.0000005] == approx([None,1]) + False + + If you're thinking about using ``approx``, then you might want to know how + it compares to other good ways of comparing floating-point numbers. All of + these algorithms are based on relative and absolute tolerances and should + agree for the most part, but they do have meaningful differences: + + - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative + tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute + tolerance is met. Because the relative tolerance is calculated w.r.t. + both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor + ``b`` is a "reference value"). You have to specify an absolute tolerance + if you want to compare to ``0.0`` because there is no tolerance by + default. More information: :py:func:`math.isclose`. + + - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference + between ``a`` and ``b`` is less that the sum of the relative tolerance + w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance + is only calculated w.r.t. ``b``, this test is asymmetric and you can + think of ``b`` as the reference value. Support for comparing sequences + is provided by :py:func:`numpy.allclose`. More information: + :std:doc:`numpy:reference/generated/numpy.isclose`. + + - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b`` + are within an absolute tolerance of ``1e-7``. No relative tolerance is + considered , so this function is not appropriate for very large or very + small numbers. Also, it's only available in subclasses of ``unittest.TestCase`` + and it's ugly because it doesn't follow PEP8. More information: + :py:meth:`unittest.TestCase.assertAlmostEqual`. + + - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative + tolerance is met w.r.t. ``b`` or if the absolute tolerance is met. + Because the relative tolerance is only calculated w.r.t. ``b``, this test + is asymmetric and you can think of ``b`` as the reference value. In the + special case that you explicitly specify an absolute tolerance but not a + relative tolerance, only the absolute tolerance is considered. + + .. note:: + + ``approx`` can handle numpy arrays, but we recommend the + specialised test helpers in :std:doc:`numpy:reference/routines.testing` + if you need support for comparisons, NaNs, or ULP-based tolerances. + + To match strings using regex, you can use + `Matches `_ + from the + `re_assert package `_. + + .. warning:: + + .. versionchanged:: 3.2 + + In order to avoid inconsistent behavior, :py:exc:`TypeError` is + raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons. + The example below illustrates the problem:: + + assert approx(0.1) > 0.1 + 1e-10 # calls approx(0.1).__gt__(0.1 + 1e-10) + assert 0.1 + 1e-10 > approx(0.1) # calls approx(0.1).__lt__(0.1 + 1e-10) + + In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)`` + to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to + comparison. This is because the call hierarchy of rich comparisons + follows a fixed behavior. More information: :py:meth:`object.__ge__` + + .. versionchanged:: 3.7.1 + ``approx`` raises ``TypeError`` when it encounters a dict value or + sequence element of nonnumeric type. + + .. versionchanged:: 6.1.0 + ``approx`` falls back to strict equality for nonnumeric types instead + of raising ``TypeError``. + """ + # Delegate the comparison to a class that knows how to deal with the type + # of the expected value (e.g. int, float, list, dict, numpy.array, etc). + # + # The primary responsibility of these classes is to implement ``__eq__()`` + # and ``__repr__()``. The former is used to actually check if some + # "actual" value is equivalent to the given expected value within the + # allowed tolerance. The latter is used to show the user the expected + # value and tolerance, in the case that a test failed. + # + # The actual logic for making approximate comparisons can be found in + # ApproxScalar, which is used to compare individual numbers. All of the + # other Approx classes eventually delegate to this class. The ApproxBase + # class provides some convenient methods and overloads, but isn't really + # essential. + + __tracebackhide__ = True + + if isinstance(expected, Decimal): + cls: type[ApproxBase] = ApproxDecimal + elif isinstance(expected, Mapping): + cls = ApproxMapping + elif _is_numpy_array(expected): + expected = _as_numpy_array(expected) + cls = ApproxNumpy + elif _is_sequence_like(expected): + cls = ApproxSequenceLike + elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)): + msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}" + raise TypeError(msg) + else: + cls = ApproxScalar + + return cls(expected, rel, abs, nan_ok) + + +def _is_sequence_like(expected: object) -> bool: + return ( + hasattr(expected, "__getitem__") + and isinstance(expected, Sized) + and not isinstance(expected, (str, bytes)) + ) + + +def _is_numpy_array(obj: object) -> bool: + """ + Return true if the given object is implicitly convertible to ndarray, + and numpy is already imported. + """ + return _as_numpy_array(obj) is not None + + +def _as_numpy_array(obj: object) -> ndarray | None: + """ + Return an ndarray if the given object is implicitly convertible to ndarray, + and numpy is already imported, otherwise None. + """ + import sys + + np: Any = sys.modules.get("numpy") + if np is not None: + # avoid infinite recursion on numpy scalars, which have __array__ + if np.isscalar(obj): + return None + elif isinstance(obj, np.ndarray): + return obj + elif hasattr(obj, "__array__") or hasattr("obj", "__array_interface__"): + return np.asarray(obj) + return None + + +# builtin pytest.raises helper + +E = TypeVar("E", bound=BaseException) + + +@overload +def raises( + expected_exception: type[E] | tuple[type[E], ...], + *, + match: str | Pattern[str] | None = ..., +) -> RaisesContext[E]: ... + + +@overload +def raises( + expected_exception: type[E] | tuple[type[E], ...], + func: Callable[..., Any], + *args: Any, + **kwargs: Any, +) -> _pytest._code.ExceptionInfo[E]: ... + + +def raises( + expected_exception: type[E] | tuple[type[E], ...], *args: Any, **kwargs: Any +) -> RaisesContext[E] | _pytest._code.ExceptionInfo[E]: + r"""Assert that a code block/function call raises an exception type, or one of its subclasses. + + :param expected_exception: + The expected exception type, or a tuple if one of multiple possible + exception types are expected. Note that subclasses of the passed exceptions + will also match. + + :kwparam str | re.Pattern[str] | None match: + If specified, a string containing a regular expression, + or a regular expression object, that is tested against the string + representation of the exception and its :pep:`678` `__notes__` + using :func:`re.search`. + + To match a literal string that may contain :ref:`special characters + `, the pattern can first be escaped with :func:`re.escape`. + + (This is only used when ``pytest.raises`` is used as a context manager, + and passed through to the function otherwise. + When using ``pytest.raises`` as a function, you can use: + ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) + + Use ``pytest.raises`` as a context manager, which will capture the exception of the given + type, or any of its subclasses:: + + >>> import pytest + >>> with pytest.raises(ZeroDivisionError): + ... 1/0 + + If the code block does not raise the expected exception (:class:`ZeroDivisionError` in the example + above), or no exception at all, the check will fail instead. + + You can also use the keyword argument ``match`` to assert that the + exception matches a text or regex:: + + >>> with pytest.raises(ValueError, match='must be 0 or None'): + ... raise ValueError("value must be 0 or None") + + >>> with pytest.raises(ValueError, match=r'must be \d+$'): + ... raise ValueError("value must be 42") + + The ``match`` argument searches the formatted exception string, which includes any + `PEP-678 `__ ``__notes__``: + + >>> with pytest.raises(ValueError, match=r"had a note added"): # doctest: +SKIP + ... e = ValueError("value must be 42") + ... e.add_note("had a note added") + ... raise e + + The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the + details of the captured exception:: + + >>> with pytest.raises(ValueError) as exc_info: + ... raise ValueError("value must be 42") + >>> assert exc_info.type is ValueError + >>> assert exc_info.value.args[0] == "value must be 42" + + .. warning:: + + Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this:: + + with pytest.raises(Exception): # Careful, this will catch ANY exception raised. + some_function() + + Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide + real bugs, where the user wrote this expecting a specific exception, but some other exception is being + raised due to a bug introduced during a refactoring. + + Avoid using ``pytest.raises`` to catch :class:`Exception` unless certain that you really want to catch + **any** exception raised. + + .. note:: + + When using ``pytest.raises`` as a context manager, it's worthwhile to + note that normal context manager rules apply and that the exception + raised *must* be the final line in the scope of the context manager. + Lines of code after that, within the scope of the context manager will + not be executed. For example:: + + >>> value = 15 + >>> with pytest.raises(ValueError) as exc_info: + ... if value > 10: + ... raise ValueError("value must be <= 10") + ... assert exc_info.type is ValueError # This will not execute. + + Instead, the following approach must be taken (note the difference in + scope):: + + >>> with pytest.raises(ValueError) as exc_info: + ... if value > 10: + ... raise ValueError("value must be <= 10") + ... + >>> assert exc_info.type is ValueError + + **Using with** ``pytest.mark.parametrize`` + + When using :ref:`pytest.mark.parametrize ref` + it is possible to parametrize tests such that + some runs raise an exception and others do not. + + See :ref:`parametrizing_conditional_raising` for an example. + + .. seealso:: + + :ref:`assertraises` for more examples and detailed discussion. + + **Legacy form** + + It is possible to specify a callable by passing a to-be-called lambda:: + + >>> raises(ZeroDivisionError, lambda: 1/0) + + + or you can specify an arbitrary callable with arguments:: + + >>> def f(x): return 1/x + ... + >>> raises(ZeroDivisionError, f, 0) + + >>> raises(ZeroDivisionError, f, x=0) + + + The form above is fully supported but discouraged for new code because the + context manager form is regarded as more readable and less error-prone. + + .. note:: + Similar to caught exception objects in Python, explicitly clearing + local references to returned ``ExceptionInfo`` objects can + help the Python interpreter speed up its garbage collection. + + Clearing those references breaks a reference cycle + (``ExceptionInfo`` --> caught exception --> frame stack raising + the exception --> current frame stack --> local variables --> + ``ExceptionInfo``) which makes Python keep all objects referenced + from that cycle (including all local variables in the current + frame) alive until the next cyclic garbage collection run. + More detailed information can be found in the official Python + documentation for :ref:`the try statement `. + """ + __tracebackhide__ = True + + if not expected_exception: + raise ValueError( + f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. " + f"Raising exceptions is already understood as failing the test, so you don't need " + f"any special code to say 'this should never raise an exception'." + ) + if isinstance(expected_exception, type): + expected_exceptions: tuple[type[E], ...] = (expected_exception,) + else: + expected_exceptions = expected_exception + for exc in expected_exceptions: + if not isinstance(exc, type) or not issubclass(exc, BaseException): + msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] + not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ + raise TypeError(msg.format(not_a)) + + message = f"DID NOT RAISE {expected_exception}" + + if not args: + match: str | Pattern[str] | None = kwargs.pop("match", None) + if kwargs: + msg = "Unexpected keyword arguments passed to pytest.raises: " + msg += ", ".join(sorted(kwargs)) + msg += "\nUse context-manager form instead?" + raise TypeError(msg) + return RaisesContext(expected_exception, message, match) + else: + func = args[0] + if not callable(func): + raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") + try: + func(*args[1:], **kwargs) + except expected_exception as e: + return _pytest._code.ExceptionInfo.from_exception(e) + fail(message) + + +# This doesn't work with mypy for now. Use fail.Exception instead. +raises.Exception = fail.Exception # type: ignore + + +@final +class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): + def __init__( + self, + expected_exception: type[E] | tuple[type[E], ...], + message: str, + match_expr: str | Pattern[str] | None = None, + ) -> None: + self.expected_exception = expected_exception + self.message = message + self.match_expr = match_expr + self.excinfo: _pytest._code.ExceptionInfo[E] | None = None + if self.match_expr is not None: + re_error = None + try: + re.compile(self.match_expr) + except re.error as e: + re_error = e + if re_error is not None: + fail(f"Invalid regex pattern provided to 'match': {re_error}") + + def __enter__(self) -> _pytest._code.ExceptionInfo[E]: + self.excinfo = _pytest._code.ExceptionInfo.for_later() + return self.excinfo + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + __tracebackhide__ = True + if exc_type is None: + fail(self.message) + assert self.excinfo is not None + if not issubclass(exc_type, self.expected_exception): + return False + # Cast to narrow the exception type now that it's verified. + exc_info = cast(Tuple[Type[E], E, TracebackType], (exc_type, exc_val, exc_tb)) + self.excinfo.fill_unfilled(exc_info) + if self.match_expr is not None: + self.excinfo.match(self.match_expr) + return True diff --git a/.venv/Lib/site-packages/_pytest/python_path.py b/.venv/Lib/site-packages/_pytest/python_path.py new file mode 100644 index 0000000..6e33c8a --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/python_path.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +import sys + +import pytest +from pytest import Config +from pytest import Parser + + +def pytest_addoption(parser: Parser) -> None: + parser.addini("pythonpath", type="paths", help="Add paths to sys.path", default=[]) + + +@pytest.hookimpl(tryfirst=True) +def pytest_load_initial_conftests(early_config: Config) -> None: + # `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]` + for path in reversed(early_config.getini("pythonpath")): + sys.path.insert(0, str(path)) + + +@pytest.hookimpl(trylast=True) +def pytest_unconfigure(config: Config) -> None: + for path in config.getini("pythonpath"): + path_str = str(path) + if path_str in sys.path: + sys.path.remove(path_str) diff --git a/.venv/Lib/site-packages/_pytest/recwarn.py b/.venv/Lib/site-packages/_pytest/recwarn.py new file mode 100644 index 0000000..0dc002e --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/recwarn.py @@ -0,0 +1,364 @@ +# mypy: allow-untyped-defs +"""Record warnings during test function execution.""" + +from __future__ import annotations + +from pprint import pformat +import re +from types import TracebackType +from typing import Any +from typing import Callable +from typing import final +from typing import Generator +from typing import Iterator +from typing import overload +from typing import Pattern +from typing import TYPE_CHECKING +from typing import TypeVar + + +if TYPE_CHECKING: + from typing_extensions import Self + +import warnings + +from _pytest.deprecated import check_ispytest +from _pytest.fixtures import fixture +from _pytest.outcomes import Exit +from _pytest.outcomes import fail + + +T = TypeVar("T") + + +@fixture +def recwarn() -> Generator[WarningsRecorder]: + """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. + + See :ref:`warnings` for information on warning categories. + """ + wrec = WarningsRecorder(_ispytest=True) + with wrec: + warnings.simplefilter("default") + yield wrec + + +@overload +def deprecated_call(*, match: str | Pattern[str] | None = ...) -> WarningsRecorder: ... + + +@overload +def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ... + + +def deprecated_call( + func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any +) -> WarningsRecorder | Any: + """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. + + This function can be used as a context manager:: + + >>> import warnings + >>> def api_call_v2(): + ... warnings.warn('use v3 of this api', DeprecationWarning) + ... return 200 + + >>> import pytest + >>> with pytest.deprecated_call(): + ... assert api_call_v2() == 200 + + It can also be used by passing a function and ``*args`` and ``**kwargs``, + in which case it will ensure calling ``func(*args, **kwargs)`` produces one of + the warnings types above. The return value is the return value of the function. + + In the context manager form you may use the keyword argument ``match`` to assert + that the warning matches a text or regex. + + The context manager produces a list of :class:`warnings.WarningMessage` objects, + one for each warning raised. + """ + __tracebackhide__ = True + if func is not None: + args = (func, *args) + return warns( + (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs + ) + + +@overload +def warns( + expected_warning: type[Warning] | tuple[type[Warning], ...] = ..., + *, + match: str | Pattern[str] | None = ..., +) -> WarningsChecker: ... + + +@overload +def warns( + expected_warning: type[Warning] | tuple[type[Warning], ...], + func: Callable[..., T], + *args: Any, + **kwargs: Any, +) -> T: ... + + +def warns( + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, + *args: Any, + match: str | Pattern[str] | None = None, + **kwargs: Any, +) -> WarningsChecker | Any: + r"""Assert that code raises a particular class of warning. + + Specifically, the parameter ``expected_warning`` can be a warning class or tuple + of warning classes, and the code inside the ``with`` block must issue at least one + warning of that class or classes. + + This helper produces a list of :class:`warnings.WarningMessage` objects, one for + each warning emitted (regardless of whether it is an ``expected_warning`` or not). + Since pytest 8.0, unmatched warnings are also re-emitted when the context closes. + + This function can be used as a context manager:: + + >>> import pytest + >>> with pytest.warns(RuntimeWarning): + ... warnings.warn("my warning", RuntimeWarning) + + In the context manager form you may use the keyword argument ``match`` to assert + that the warning matches a text or regex:: + + >>> with pytest.warns(UserWarning, match='must be 0 or None'): + ... warnings.warn("value must be 0 or None", UserWarning) + + >>> with pytest.warns(UserWarning, match=r'must be \d+$'): + ... warnings.warn("value must be 42", UserWarning) + + >>> with pytest.warns(UserWarning): # catch re-emitted warning + ... with pytest.warns(UserWarning, match=r'must be \d+$'): + ... warnings.warn("this is not here", UserWarning) + Traceback (most recent call last): + ... + Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... + + **Using with** ``pytest.mark.parametrize`` + + When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests + such that some runs raise a warning and others do not. + + This could be achieved in the same way as with exceptions, see + :ref:`parametrizing_conditional_raising` for an example. + + """ + __tracebackhide__ = True + if not args: + if kwargs: + argnames = ", ".join(sorted(kwargs)) + raise TypeError( + f"Unexpected keyword arguments passed to pytest.warns: {argnames}" + "\nUse context-manager form instead?" + ) + return WarningsChecker(expected_warning, match_expr=match, _ispytest=True) + else: + func = args[0] + if not callable(func): + raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") + with WarningsChecker(expected_warning, _ispytest=True): + return func(*args[1:], **kwargs) + + +class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] + """A context manager to record raised warnings. + + Each recorded warning is an instance of :class:`warnings.WarningMessage`. + + Adapted from `warnings.catch_warnings`. + + .. note:: + ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated + differently; see :ref:`ensuring_function_triggers`. + + """ + + def __init__(self, *, _ispytest: bool = False) -> None: + check_ispytest(_ispytest) + super().__init__(record=True) + self._entered = False + self._list: list[warnings.WarningMessage] = [] + + @property + def list(self) -> list[warnings.WarningMessage]: + """The list of recorded warnings.""" + return self._list + + def __getitem__(self, i: int) -> warnings.WarningMessage: + """Get a recorded warning by index.""" + return self._list[i] + + def __iter__(self) -> Iterator[warnings.WarningMessage]: + """Iterate through the recorded warnings.""" + return iter(self._list) + + def __len__(self) -> int: + """The number of recorded warnings.""" + return len(self._list) + + def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage: + """Pop the first recorded warning which is an instance of ``cls``, + but not an instance of a child class of any other match. + Raises ``AssertionError`` if there is no match. + """ + best_idx: int | None = None + for i, w in enumerate(self._list): + if w.category == cls: + return self._list.pop(i) # exact match, stop looking + if issubclass(w.category, cls) and ( + best_idx is None + or not issubclass(w.category, self._list[best_idx].category) + ): + best_idx = i + if best_idx is not None: + return self._list.pop(best_idx) + __tracebackhide__ = True + raise AssertionError(f"{cls!r} not found in warning list") + + def clear(self) -> None: + """Clear the list of recorded warnings.""" + self._list[:] = [] + + def __enter__(self) -> Self: + if self._entered: + __tracebackhide__ = True + raise RuntimeError(f"Cannot enter {self!r} twice") + _list = super().__enter__() + # record=True means it's None. + assert _list is not None + self._list = _list + warnings.simplefilter("always") + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if not self._entered: + __tracebackhide__ = True + raise RuntimeError(f"Cannot exit {self!r} without entering first") + + super().__exit__(exc_type, exc_val, exc_tb) + + # Built-in catch_warnings does not reset entered state so we do it + # manually here for this context manager to become reusable. + self._entered = False + + +@final +class WarningsChecker(WarningsRecorder): + def __init__( + self, + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, + match_expr: str | Pattern[str] | None = None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + super().__init__(_ispytest=True) + + msg = "exceptions must be derived from Warning, not %s" + if isinstance(expected_warning, tuple): + for exc in expected_warning: + if not issubclass(exc, Warning): + raise TypeError(msg % type(exc)) + expected_warning_tup = expected_warning + elif isinstance(expected_warning, type) and issubclass( + expected_warning, Warning + ): + expected_warning_tup = (expected_warning,) + else: + raise TypeError(msg % type(expected_warning)) + + self.expected_warning = expected_warning_tup + self.match_expr = match_expr + + def matches(self, warning: warnings.WarningMessage) -> bool: + assert self.expected_warning is not None + return issubclass(warning.category, self.expected_warning) and bool( + self.match_expr is None or re.search(self.match_expr, str(warning.message)) + ) + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + super().__exit__(exc_type, exc_val, exc_tb) + + __tracebackhide__ = True + + # BaseExceptions like pytest.{skip,fail,xfail,exit} or Ctrl-C within + # pytest.warns should *not* trigger "DID NOT WARN" and get suppressed + # when the warning doesn't happen. Control-flow exceptions should always + # propagate. + if exc_val is not None and ( + not isinstance(exc_val, Exception) + # Exit is an Exception, not a BaseException, for some reason. + or isinstance(exc_val, Exit) + ): + return + + def found_str() -> str: + return pformat([record.message for record in self], indent=2) + + try: + if not any(issubclass(w.category, self.expected_warning) for w in self): + fail( + f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n" + f" Emitted warnings: {found_str()}." + ) + elif not any(self.matches(w) for w in self): + fail( + f"DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.\n" + f" Regex: {self.match_expr}\n" + f" Emitted warnings: {found_str()}." + ) + finally: + # Whether or not any warnings matched, we want to re-emit all unmatched warnings. + for w in self: + if not self.matches(w): + warnings.warn_explicit( + message=w.message, + category=w.category, + filename=w.filename, + lineno=w.lineno, + module=w.__module__, + source=w.source, + ) + + # Currently in Python it is possible to pass other types than an + # `str` message when creating `Warning` instances, however this + # causes an exception when :func:`warnings.filterwarnings` is used + # to filter those warnings. See + # https://github.com/python/cpython/issues/103577 for a discussion. + # While this can be considered a bug in CPython, we put guards in + # pytest as the error message produced without this check in place + # is confusing (#10865). + for w in self: + if type(w.message) is not UserWarning: + # If the warning was of an incorrect type then `warnings.warn()` + # creates a UserWarning. Any other warning must have been specified + # explicitly. + continue + if not w.message.args: + # UserWarning() without arguments must have been specified explicitly. + continue + msg = w.message.args[0] + if isinstance(msg, str): + continue + # It's possible that UserWarning was explicitly specified, and + # its first argument was not a string. But that case can't be + # distinguished from an invalid type. + raise TypeError( + f"Warning must be str or Warning, got {msg!r} (type {type(msg).__name__})" + ) diff --git a/.venv/Lib/site-packages/_pytest/reports.py b/.venv/Lib/site-packages/_pytest/reports.py new file mode 100644 index 0000000..77cbf77 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/reports.py @@ -0,0 +1,636 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +import dataclasses +from io import StringIO +import os +from pprint import pprint +from typing import Any +from typing import cast +from typing import final +from typing import Iterable +from typing import Iterator +from typing import Literal +from typing import Mapping +from typing import NoReturn +from typing import Sequence +from typing import TYPE_CHECKING + +from _pytest._code.code import ExceptionChainRepr +from _pytest._code.code import ExceptionInfo +from _pytest._code.code import ExceptionRepr +from _pytest._code.code import ReprEntry +from _pytest._code.code import ReprEntryNative +from _pytest._code.code import ReprExceptionInfo +from _pytest._code.code import ReprFileLocation +from _pytest._code.code import ReprFuncArgs +from _pytest._code.code import ReprLocals +from _pytest._code.code import ReprTraceback +from _pytest._code.code import TerminalRepr +from _pytest._io import TerminalWriter +from _pytest.config import Config +from _pytest.nodes import Collector +from _pytest.nodes import Item +from _pytest.outcomes import fail +from _pytest.outcomes import skip + + +if TYPE_CHECKING: + from typing_extensions import Self + + from _pytest.runner import CallInfo + + +def getworkerinfoline(node): + try: + return node._workerinfocache + except AttributeError: + d = node.workerinfo + ver = "{}.{}.{}".format(*d["version_info"][:3]) + node._workerinfocache = s = "[{}] {} -- Python {} {}".format( + d["id"], d["sysplatform"], ver, d["executable"] + ) + return s + + +class BaseReport: + when: str | None + location: tuple[str, int | None, str] | None + longrepr: ( + None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr + ) + sections: list[tuple[str, str]] + nodeid: str + outcome: Literal["passed", "failed", "skipped"] + + def __init__(self, **kw: Any) -> None: + self.__dict__.update(kw) + + if TYPE_CHECKING: + # Can have arbitrary fields given to __init__(). + def __getattr__(self, key: str) -> Any: ... + + def toterminal(self, out: TerminalWriter) -> None: + if hasattr(self, "node"): + worker_info = getworkerinfoline(self.node) + if worker_info: + out.line(worker_info) + + longrepr = self.longrepr + if longrepr is None: + return + + if hasattr(longrepr, "toterminal"): + longrepr_terminal = cast(TerminalRepr, longrepr) + longrepr_terminal.toterminal(out) + else: + try: + s = str(longrepr) + except UnicodeEncodeError: + s = "" + out.line(s) + + def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]: + for name, content in self.sections: + if name.startswith(prefix): + yield prefix, content + + @property + def longreprtext(self) -> str: + """Read-only property that returns the full string representation of + ``longrepr``. + + .. versionadded:: 3.0 + """ + file = StringIO() + tw = TerminalWriter(file) + tw.hasmarkup = False + self.toterminal(tw) + exc = file.getvalue() + return exc.strip() + + @property + def caplog(self) -> str: + """Return captured log lines, if log capturing is enabled. + + .. versionadded:: 3.5 + """ + return "\n".join( + content for (prefix, content) in self.get_sections("Captured log") + ) + + @property + def capstdout(self) -> str: + """Return captured text from stdout, if capturing is enabled. + + .. versionadded:: 3.0 + """ + return "".join( + content for (prefix, content) in self.get_sections("Captured stdout") + ) + + @property + def capstderr(self) -> str: + """Return captured text from stderr, if capturing is enabled. + + .. versionadded:: 3.0 + """ + return "".join( + content for (prefix, content) in self.get_sections("Captured stderr") + ) + + @property + def passed(self) -> bool: + """Whether the outcome is passed.""" + return self.outcome == "passed" + + @property + def failed(self) -> bool: + """Whether the outcome is failed.""" + return self.outcome == "failed" + + @property + def skipped(self) -> bool: + """Whether the outcome is skipped.""" + return self.outcome == "skipped" + + @property + def fspath(self) -> str: + """The path portion of the reported node, as a string.""" + return self.nodeid.split("::")[0] + + @property + def count_towards_summary(self) -> bool: + """**Experimental** Whether this report should be counted towards the + totals shown at the end of the test session: "1 passed, 1 failure, etc". + + .. note:: + + This function is considered **experimental**, so beware that it is subject to changes + even in patch releases. + """ + return True + + @property + def head_line(self) -> str | None: + """**Experimental** The head line shown with longrepr output for this + report, more commonly during traceback representation during + failures:: + + ________ Test.foo ________ + + + In the example above, the head_line is "Test.foo". + + .. note:: + + This function is considered **experimental**, so beware that it is subject to changes + even in patch releases. + """ + if self.location is not None: + fspath, lineno, domain = self.location + return domain + return None + + def _get_verbose_word_with_markup( + self, config: Config, default_markup: Mapping[str, bool] + ) -> tuple[str, Mapping[str, bool]]: + _category, _short, verbose = config.hook.pytest_report_teststatus( + report=self, config=config + ) + + if isinstance(verbose, str): + return verbose, default_markup + + if isinstance(verbose, Sequence) and len(verbose) == 2: + word, markup = verbose + if isinstance(word, str) and isinstance(markup, Mapping): + return word, markup + + fail( # pragma: no cover + "pytest_report_teststatus() hook (from a plugin) returned " + f"an invalid verbose value: {verbose!r}.\nExpected either a string " + "or a tuple of (word, markup)." + ) + + def _to_json(self) -> dict[str, Any]: + """Return the contents of this report as a dict of builtin entries, + suitable for serialization. + + This was originally the serialize_report() function from xdist (ca03269). + + Experimental method. + """ + return _report_to_json(self) + + @classmethod + def _from_json(cls, reportdict: dict[str, object]) -> Self: + """Create either a TestReport or CollectReport, depending on the calling class. + + It is the callers responsibility to know which class to pass here. + + This was originally the serialize_report() function from xdist (ca03269). + + Experimental method. + """ + kwargs = _report_kwargs_from_json(reportdict) + return cls(**kwargs) + + +def _report_unserialization_failure( + type_name: str, report_class: type[BaseReport], reportdict +) -> NoReturn: + url = "https://github.com/pytest-dev/pytest/issues" + stream = StringIO() + pprint("-" * 100, stream=stream) + pprint(f"INTERNALERROR: Unknown entry type returned: {type_name}", stream=stream) + pprint(f"report_name: {report_class}", stream=stream) + pprint(reportdict, stream=stream) + pprint(f"Please report this bug at {url}", stream=stream) + pprint("-" * 100, stream=stream) + raise RuntimeError(stream.getvalue()) + + +@final +class TestReport(BaseReport): + """Basic test report object (also used for setup and teardown calls if + they fail). + + Reports can contain arbitrary extra attributes. + """ + + __test__ = False + # Defined by skipping plugin. + # xfail reason if xfailed, otherwise not defined. Use hasattr to distinguish. + wasxfail: str + + def __init__( + self, + nodeid: str, + location: tuple[str, int | None, str], + keywords: Mapping[str, Any], + outcome: Literal["passed", "failed", "skipped"], + longrepr: None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr, + when: Literal["setup", "call", "teardown"], + sections: Iterable[tuple[str, str]] = (), + duration: float = 0, + start: float = 0, + stop: float = 0, + user_properties: Iterable[tuple[str, object]] | None = None, + **extra, + ) -> None: + #: Normalized collection nodeid. + self.nodeid = nodeid + + #: A (filesystempath, lineno, domaininfo) tuple indicating the + #: actual location of a test item - it might be different from the + #: collected one e.g. if a method is inherited from a different module. + #: The filesystempath may be relative to ``config.rootdir``. + #: The line number is 0-based. + self.location: tuple[str, int | None, str] = location + + #: A name -> value dictionary containing all keywords and + #: markers associated with a test invocation. + self.keywords: Mapping[str, Any] = keywords + + #: Test outcome, always one of "passed", "failed", "skipped". + self.outcome = outcome + + #: None or a failure representation. + self.longrepr = longrepr + + #: One of 'setup', 'call', 'teardown' to indicate runtest phase. + self.when = when + + #: User properties is a list of tuples (name, value) that holds user + #: defined properties of the test. + self.user_properties = list(user_properties or []) + + #: Tuples of str ``(heading, content)`` with extra information + #: for the test report. Used by pytest to add text captured + #: from ``stdout``, ``stderr``, and intercepted logging events. May + #: be used by other plugins to add arbitrary information to reports. + self.sections = list(sections) + + #: Time it took to run just the test. + self.duration: float = duration + + #: The system time when the call started, in seconds since the epoch. + self.start: float = start + #: The system time when the call ended, in seconds since the epoch. + self.stop: float = stop + + self.__dict__.update(extra) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>" + + @classmethod + def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: + """Create and fill a TestReport with standard item and call info. + + :param item: The item. + :param call: The call info. + """ + when = call.when + # Remove "collect" from the Literal type -- only for collection calls. + assert when != "collect" + duration = call.duration + start = call.start + stop = call.stop + keywords = {x: 1 for x in item.keywords} + excinfo = call.excinfo + sections = [] + if not call.excinfo: + outcome: Literal["passed", "failed", "skipped"] = "passed" + longrepr: ( + None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr + ) = None + else: + if not isinstance(excinfo, ExceptionInfo): + outcome = "failed" + longrepr = excinfo + elif isinstance(excinfo.value, skip.Exception): + outcome = "skipped" + r = excinfo._getreprcrash() + assert ( + r is not None + ), "There should always be a traceback entry for skipping a test." + if excinfo.value._use_item_location: + path, line = item.reportinfo()[:2] + assert line is not None + longrepr = os.fspath(path), line + 1, r.message + else: + longrepr = (str(r.path), r.lineno, r.message) + else: + outcome = "failed" + if call.when == "call": + longrepr = item.repr_failure(excinfo) + else: # exception in setup or teardown + longrepr = item._repr_failure_py( + excinfo, style=item.config.getoption("tbstyle", "auto") + ) + for rwhen, key, content in item._report_sections: + sections.append((f"Captured {key} {rwhen}", content)) + return cls( + item.nodeid, + item.location, + keywords, + outcome, + longrepr, + when, + sections, + duration, + start, + stop, + user_properties=item.user_properties, + ) + + +@final +class CollectReport(BaseReport): + """Collection report object. + + Reports can contain arbitrary extra attributes. + """ + + when = "collect" + + def __init__( + self, + nodeid: str, + outcome: Literal["passed", "failed", "skipped"], + longrepr: None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr, + result: list[Item | Collector] | None, + sections: Iterable[tuple[str, str]] = (), + **extra, + ) -> None: + #: Normalized collection nodeid. + self.nodeid = nodeid + + #: Test outcome, always one of "passed", "failed", "skipped". + self.outcome = outcome + + #: None or a failure representation. + self.longrepr = longrepr + + #: The collected items and collection nodes. + self.result = result or [] + + #: Tuples of str ``(heading, content)`` with extra information + #: for the test report. Used by pytest to add text captured + #: from ``stdout``, ``stderr``, and intercepted logging events. May + #: be used by other plugins to add arbitrary information to reports. + self.sections = list(sections) + + self.__dict__.update(extra) + + @property + def location( # type:ignore[override] + self, + ) -> tuple[str, int | None, str] | None: + return (self.fspath, None, self.fspath) + + def __repr__(self) -> str: + return f"" + + +class CollectErrorRepr(TerminalRepr): + def __init__(self, msg: str) -> None: + self.longrepr = msg + + def toterminal(self, out: TerminalWriter) -> None: + out.line(self.longrepr, red=True) + + +def pytest_report_to_serializable( + report: CollectReport | TestReport, +) -> dict[str, Any] | None: + if isinstance(report, (TestReport, CollectReport)): + data = report._to_json() + data["$report_type"] = report.__class__.__name__ + return data + # TODO: Check if this is actually reachable. + return None # type: ignore[unreachable] + + +def pytest_report_from_serializable( + data: dict[str, Any], +) -> CollectReport | TestReport | None: + if "$report_type" in data: + if data["$report_type"] == "TestReport": + return TestReport._from_json(data) + elif data["$report_type"] == "CollectReport": + return CollectReport._from_json(data) + assert False, "Unknown report_type unserialize data: {}".format( + data["$report_type"] + ) + return None + + +def _report_to_json(report: BaseReport) -> dict[str, Any]: + """Return the contents of this report as a dict of builtin entries, + suitable for serialization. + + This was originally the serialize_report() function from xdist (ca03269). + """ + + def serialize_repr_entry( + entry: ReprEntry | ReprEntryNative, + ) -> dict[str, Any]: + data = dataclasses.asdict(entry) + for key, value in data.items(): + if hasattr(value, "__dict__"): + data[key] = dataclasses.asdict(value) + entry_data = {"type": type(entry).__name__, "data": data} + return entry_data + + def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]: + result = dataclasses.asdict(reprtraceback) + result["reprentries"] = [ + serialize_repr_entry(x) for x in reprtraceback.reprentries + ] + return result + + def serialize_repr_crash( + reprcrash: ReprFileLocation | None, + ) -> dict[str, Any] | None: + if reprcrash is not None: + return dataclasses.asdict(reprcrash) + else: + return None + + def serialize_exception_longrepr(rep: BaseReport) -> dict[str, Any]: + assert rep.longrepr is not None + # TODO: Investigate whether the duck typing is really necessary here. + longrepr = cast(ExceptionRepr, rep.longrepr) + result: dict[str, Any] = { + "reprcrash": serialize_repr_crash(longrepr.reprcrash), + "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback), + "sections": longrepr.sections, + } + if isinstance(longrepr, ExceptionChainRepr): + result["chain"] = [] + for repr_traceback, repr_crash, description in longrepr.chain: + result["chain"].append( + ( + serialize_repr_traceback(repr_traceback), + serialize_repr_crash(repr_crash), + description, + ) + ) + else: + result["chain"] = None + return result + + d = report.__dict__.copy() + if hasattr(report.longrepr, "toterminal"): + if hasattr(report.longrepr, "reprtraceback") and hasattr( + report.longrepr, "reprcrash" + ): + d["longrepr"] = serialize_exception_longrepr(report) + else: + d["longrepr"] = str(report.longrepr) + else: + d["longrepr"] = report.longrepr + for name in d: + if isinstance(d[name], os.PathLike): + d[name] = os.fspath(d[name]) + elif name == "result": + d[name] = None # for now + return d + + +def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: + """Return **kwargs that can be used to construct a TestReport or + CollectReport instance. + + This was originally the serialize_report() function from xdist (ca03269). + """ + + def deserialize_repr_entry(entry_data): + data = entry_data["data"] + entry_type = entry_data["type"] + if entry_type == "ReprEntry": + reprfuncargs = None + reprfileloc = None + reprlocals = None + if data["reprfuncargs"]: + reprfuncargs = ReprFuncArgs(**data["reprfuncargs"]) + if data["reprfileloc"]: + reprfileloc = ReprFileLocation(**data["reprfileloc"]) + if data["reprlocals"]: + reprlocals = ReprLocals(data["reprlocals"]["lines"]) + + reprentry: ReprEntry | ReprEntryNative = ReprEntry( + lines=data["lines"], + reprfuncargs=reprfuncargs, + reprlocals=reprlocals, + reprfileloc=reprfileloc, + style=data["style"], + ) + elif entry_type == "ReprEntryNative": + reprentry = ReprEntryNative(data["lines"]) + else: + _report_unserialization_failure(entry_type, TestReport, reportdict) + return reprentry + + def deserialize_repr_traceback(repr_traceback_dict): + repr_traceback_dict["reprentries"] = [ + deserialize_repr_entry(x) for x in repr_traceback_dict["reprentries"] + ] + return ReprTraceback(**repr_traceback_dict) + + def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None): + if repr_crash_dict is not None: + return ReprFileLocation(**repr_crash_dict) + else: + return None + + if ( + reportdict["longrepr"] + and "reprcrash" in reportdict["longrepr"] + and "reprtraceback" in reportdict["longrepr"] + ): + reprtraceback = deserialize_repr_traceback( + reportdict["longrepr"]["reprtraceback"] + ) + reprcrash = deserialize_repr_crash(reportdict["longrepr"]["reprcrash"]) + if reportdict["longrepr"]["chain"]: + chain = [] + for repr_traceback_data, repr_crash_data, description in reportdict[ + "longrepr" + ]["chain"]: + chain.append( + ( + deserialize_repr_traceback(repr_traceback_data), + deserialize_repr_crash(repr_crash_data), + description, + ) + ) + exception_info: ExceptionChainRepr | ReprExceptionInfo = ExceptionChainRepr( + chain + ) + else: + exception_info = ReprExceptionInfo( + reprtraceback=reprtraceback, + reprcrash=reprcrash, + ) + + for section in reportdict["longrepr"]["sections"]: + exception_info.addsection(*section) + reportdict["longrepr"] = exception_info + + return reportdict diff --git a/.venv/Lib/site-packages/_pytest/runner.py b/.venv/Lib/site-packages/_pytest/runner.py new file mode 100644 index 0000000..0b60301 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/runner.py @@ -0,0 +1,571 @@ +# mypy: allow-untyped-defs +"""Basic collect and runtest protocol implementations.""" + +from __future__ import annotations + +import bdb +import dataclasses +import os +import sys +import types +from typing import Callable +from typing import cast +from typing import final +from typing import Generic +from typing import Literal +from typing import TYPE_CHECKING +from typing import TypeVar + +from .reports import BaseReport +from .reports import CollectErrorRepr +from .reports import CollectReport +from .reports import TestReport +from _pytest import timing +from _pytest._code.code import ExceptionChainRepr +from _pytest._code.code import ExceptionInfo +from _pytest._code.code import TerminalRepr +from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest +from _pytest.nodes import Collector +from _pytest.nodes import Directory +from _pytest.nodes import Item +from _pytest.nodes import Node +from _pytest.outcomes import Exit +from _pytest.outcomes import OutcomeException +from _pytest.outcomes import Skipped +from _pytest.outcomes import TEST_OUTCOME + + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + +if TYPE_CHECKING: + from _pytest.main import Session + from _pytest.terminal import TerminalReporter + +# +# pytest plugin hooks. + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("terminal reporting", "Reporting", after="general") + group.addoption( + "--durations", + action="store", + type=int, + default=None, + metavar="N", + help="Show N slowest setup/test durations (N=0 for all)", + ) + group.addoption( + "--durations-min", + action="store", + type=float, + default=0.005, + metavar="N", + help="Minimal duration in seconds for inclusion in slowest list. " + "Default: 0.005.", + ) + + +def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: + durations = terminalreporter.config.option.durations + durations_min = terminalreporter.config.option.durations_min + verbose = terminalreporter.config.get_verbosity() + if durations is None: + return + tr = terminalreporter + dlist = [] + for replist in tr.stats.values(): + for rep in replist: + if hasattr(rep, "duration"): + dlist.append(rep) + if not dlist: + return + dlist.sort(key=lambda x: x.duration, reverse=True) + if not durations: + tr.write_sep("=", "slowest durations") + else: + tr.write_sep("=", f"slowest {durations} durations") + dlist = dlist[:durations] + + for i, rep in enumerate(dlist): + if verbose < 2 and rep.duration < durations_min: + tr.write_line("") + tr.write_line( + f"({len(dlist) - i} durations < {durations_min:g}s hidden. Use -vv to show these durations.)" + ) + break + tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") + + +def pytest_sessionstart(session: Session) -> None: + session._setupstate = SetupState() + + +def pytest_sessionfinish(session: Session) -> None: + session._setupstate.teardown_exact(None) + + +def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool: + ihook = item.ihook + ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) + runtestprotocol(item, nextitem=nextitem) + ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location) + return True + + +def runtestprotocol( + item: Item, log: bool = True, nextitem: Item | None = None +) -> list[TestReport]: + hasrequest = hasattr(item, "_request") + if hasrequest and not item._request: # type: ignore[attr-defined] + # This only happens if the item is re-run, as is done by + # pytest-rerunfailures. + item._initrequest() # type: ignore[attr-defined] + rep = call_and_report(item, "setup", log) + reports = [rep] + if rep.passed: + if item.config.getoption("setupshow", False): + show_test_item(item) + if not item.config.getoption("setuponly", False): + reports.append(call_and_report(item, "call", log)) + # If the session is about to fail or stop, teardown everything - this is + # necessary to correctly report fixture teardown errors (see #11706) + if item.session.shouldfail or item.session.shouldstop: + nextitem = None + reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) + # After all teardown hooks have been called + # want funcargs and request info to go away. + if hasrequest: + item._request = False # type: ignore[attr-defined] + item.funcargs = None # type: ignore[attr-defined] + return reports + + +def show_test_item(item: Item) -> None: + """Show test function, parameters and the fixtures of the test item.""" + tw = item.config.get_terminal_writer() + tw.line() + tw.write(" " * 8) + tw.write(item.nodeid) + used_fixtures = sorted(getattr(item, "fixturenames", [])) + if used_fixtures: + tw.write(" (fixtures used: {})".format(", ".join(used_fixtures))) + tw.flush() + + +def pytest_runtest_setup(item: Item) -> None: + _update_current_test_var(item, "setup") + item.session._setupstate.setup(item) + + +def pytest_runtest_call(item: Item) -> None: + _update_current_test_var(item, "call") + try: + del sys.last_type + del sys.last_value + del sys.last_traceback + if sys.version_info >= (3, 12, 0): + del sys.last_exc # type:ignore[attr-defined] + except AttributeError: + pass + try: + item.runtest() + except Exception as e: + # Store trace info to allow postmortem debugging + sys.last_type = type(e) + sys.last_value = e + if sys.version_info >= (3, 12, 0): + sys.last_exc = e # type:ignore[attr-defined] + assert e.__traceback__ is not None + # Skip *this* frame + sys.last_traceback = e.__traceback__.tb_next + raise + + +def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: + _update_current_test_var(item, "teardown") + item.session._setupstate.teardown_exact(nextitem) + _update_current_test_var(item, None) + + +def _update_current_test_var( + item: Item, when: Literal["setup", "call", "teardown"] | None +) -> None: + """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. + + If ``when`` is None, delete ``PYTEST_CURRENT_TEST`` from the environment. + """ + var_name = "PYTEST_CURRENT_TEST" + if when: + value = f"{item.nodeid} ({when})" + # don't allow null bytes on environment variables (see #2644, #2957) + value = value.replace("\x00", "(null)") + os.environ[var_name] = value + else: + os.environ.pop(var_name) + + +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: + if report.when in ("setup", "teardown"): + if report.failed: + # category, shortletter, verbose-word + return "error", "E", "ERROR" + elif report.skipped: + return "skipped", "s", "SKIPPED" + else: + return "", "", "" + return None + + +# +# Implementation + + +def call_and_report( + item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds +) -> TestReport: + ihook = item.ihook + if when == "setup": + runtest_hook: Callable[..., None] = ihook.pytest_runtest_setup + elif when == "call": + runtest_hook = ihook.pytest_runtest_call + elif when == "teardown": + runtest_hook = ihook.pytest_runtest_teardown + else: + assert False, f"Unhandled runtest hook case: {when}" + reraise: tuple[type[BaseException], ...] = (Exit,) + if not item.config.getoption("usepdb", False): + reraise += (KeyboardInterrupt,) + call = CallInfo.from_call( + lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise + ) + report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call) + if log: + ihook.pytest_runtest_logreport(report=report) + if check_interactive_exception(call, report): + ihook.pytest_exception_interact(node=item, call=call, report=report) + return report + + +def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> bool: + """Check whether the call raised an exception that should be reported as + interactive.""" + if call.excinfo is None: + # Didn't raise. + return False + if hasattr(report, "wasxfail"): + # Exception was expected. + return False + if isinstance(call.excinfo.value, (Skipped, bdb.BdbQuit)): + # Special control flow exception. + return False + return True + + +TResult = TypeVar("TResult", covariant=True) + + +@final +@dataclasses.dataclass +class CallInfo(Generic[TResult]): + """Result/Exception info of a function invocation.""" + + _result: TResult | None + #: The captured exception of the call, if it raised. + excinfo: ExceptionInfo[BaseException] | None + #: The system time when the call started, in seconds since the epoch. + start: float + #: The system time when the call ended, in seconds since the epoch. + stop: float + #: The call duration, in seconds. + duration: float + #: The context of invocation: "collect", "setup", "call" or "teardown". + when: Literal["collect", "setup", "call", "teardown"] + + def __init__( + self, + result: TResult | None, + excinfo: ExceptionInfo[BaseException] | None, + start: float, + stop: float, + duration: float, + when: Literal["collect", "setup", "call", "teardown"], + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + self._result = result + self.excinfo = excinfo + self.start = start + self.stop = stop + self.duration = duration + self.when = when + + @property + def result(self) -> TResult: + """The return value of the call, if it didn't raise. + + Can only be accessed if excinfo is None. + """ + if self.excinfo is not None: + raise AttributeError(f"{self!r} has no valid result") + # The cast is safe because an exception wasn't raised, hence + # _result has the expected function return type (which may be + # None, that's why a cast and not an assert). + return cast(TResult, self._result) + + @classmethod + def from_call( + cls, + func: Callable[[], TResult], + when: Literal["collect", "setup", "call", "teardown"], + reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None, + ) -> CallInfo[TResult]: + """Call func, wrapping the result in a CallInfo. + + :param func: + The function to call. Called without arguments. + :type func: Callable[[], _pytest.runner.TResult] + :param when: + The phase in which the function is called. + :param reraise: + Exception or exceptions that shall propagate if raised by the + function, instead of being wrapped in the CallInfo. + """ + excinfo = None + start = timing.time() + precise_start = timing.perf_counter() + try: + result: TResult | None = func() + except BaseException: + excinfo = ExceptionInfo.from_current() + if reraise is not None and isinstance(excinfo.value, reraise): + raise + result = None + # use the perf counter + precise_stop = timing.perf_counter() + duration = precise_stop - precise_start + stop = timing.time() + return cls( + start=start, + stop=stop, + duration=duration, + when=when, + result=result, + excinfo=excinfo, + _ispytest=True, + ) + + def __repr__(self) -> str: + if self.excinfo is None: + return f"" + return f"" + + +def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: + return TestReport.from_item_and_call(item, call) + + +def pytest_make_collect_report(collector: Collector) -> CollectReport: + def collect() -> list[Item | Collector]: + # Before collecting, if this is a Directory, load the conftests. + # If a conftest import fails to load, it is considered a collection + # error of the Directory collector. This is why it's done inside of the + # CallInfo wrapper. + # + # Note: initial conftests are loaded early, not here. + if isinstance(collector, Directory): + collector.config.pluginmanager._loadconftestmodules( + collector.path, + collector.config.getoption("importmode"), + rootpath=collector.config.rootpath, + consider_namespace_packages=collector.config.getini( + "consider_namespace_packages" + ), + ) + + return list(collector.collect()) + + call = CallInfo.from_call( + collect, "collect", reraise=(KeyboardInterrupt, SystemExit) + ) + longrepr: None | tuple[str, int, str] | str | TerminalRepr = None + if not call.excinfo: + outcome: Literal["passed", "skipped", "failed"] = "passed" + else: + skip_exceptions = [Skipped] + unittest = sys.modules.get("unittest") + if unittest is not None: + skip_exceptions.append(unittest.SkipTest) + if isinstance(call.excinfo.value, tuple(skip_exceptions)): + outcome = "skipped" + r_ = collector._repr_failure_py(call.excinfo, "line") + assert isinstance(r_, ExceptionChainRepr), repr(r_) + r = r_.reprcrash + assert r + longrepr = (str(r.path), r.lineno, r.message) + else: + outcome = "failed" + errorinfo = collector.repr_failure(call.excinfo) + if not hasattr(errorinfo, "toterminal"): + assert isinstance(errorinfo, str) + errorinfo = CollectErrorRepr(errorinfo) + longrepr = errorinfo + result = call.result if not call.excinfo else None + rep = CollectReport(collector.nodeid, outcome, longrepr, result) + rep.call = call # type: ignore # see collect_one_node + return rep + + +class SetupState: + """Shared state for setting up/tearing down test items or collectors + in a session. + + Suppose we have a collection tree as follows: + + + + + + + + The SetupState maintains a stack. The stack starts out empty: + + [] + + During the setup phase of item1, setup(item1) is called. What it does + is: + + push session to stack, run session.setup() + push mod1 to stack, run mod1.setup() + push item1 to stack, run item1.setup() + + The stack is: + + [session, mod1, item1] + + While the stack is in this shape, it is allowed to add finalizers to + each of session, mod1, item1 using addfinalizer(). + + During the teardown phase of item1, teardown_exact(item2) is called, + where item2 is the next item to item1. What it does is: + + pop item1 from stack, run its teardowns + pop mod1 from stack, run its teardowns + + mod1 was popped because it ended its purpose with item1. The stack is: + + [session] + + During the setup phase of item2, setup(item2) is called. What it does + is: + + push mod2 to stack, run mod2.setup() + push item2 to stack, run item2.setup() + + Stack: + + [session, mod2, item2] + + During the teardown phase of item2, teardown_exact(None) is called, + because item2 is the last item. What it does is: + + pop item2 from stack, run its teardowns + pop mod2 from stack, run its teardowns + pop session from stack, run its teardowns + + Stack: + + [] + + The end! + """ + + def __init__(self) -> None: + # The stack is in the dict insertion order. + self.stack: dict[ + Node, + tuple[ + # Node's finalizers. + list[Callable[[], object]], + # Node's exception and original traceback, if its setup raised. + tuple[OutcomeException | Exception, types.TracebackType | None] | None, + ], + ] = {} + + def setup(self, item: Item) -> None: + """Setup objects along the collector chain to the item.""" + needed_collectors = item.listchain() + + # If a collector fails its setup, fail its entire subtree of items. + # The setup is not retried for each item - the same exception is used. + for col, (finalizers, exc) in self.stack.items(): + assert col in needed_collectors, "previous item was not torn down properly" + if exc: + raise exc[0].with_traceback(exc[1]) + + for col in needed_collectors[len(self.stack) :]: + assert col not in self.stack + # Push onto the stack. + self.stack[col] = ([col.teardown], None) + try: + col.setup() + except TEST_OUTCOME as exc: + self.stack[col] = (self.stack[col][0], (exc, exc.__traceback__)) + raise + + def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: + """Attach a finalizer to the given node. + + The node must be currently active in the stack. + """ + assert node and not isinstance(node, tuple) + assert callable(finalizer) + assert node in self.stack, (node, self.stack) + self.stack[node][0].append(finalizer) + + def teardown_exact(self, nextitem: Item | None) -> None: + """Teardown the current stack up until reaching nodes that nextitem + also descends from. + + When nextitem is None (meaning we're at the last item), the entire + stack is torn down. + """ + needed_collectors = nextitem and nextitem.listchain() or [] + exceptions: list[BaseException] = [] + while self.stack: + if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: + break + node, (finalizers, _) = self.stack.popitem() + these_exceptions = [] + while finalizers: + fin = finalizers.pop() + try: + fin() + except TEST_OUTCOME as e: + these_exceptions.append(e) + + if len(these_exceptions) == 1: + exceptions.extend(these_exceptions) + elif these_exceptions: + msg = f"errors while tearing down {node!r}" + exceptions.append(BaseExceptionGroup(msg, these_exceptions[::-1])) + + if len(exceptions) == 1: + raise exceptions[0] + elif exceptions: + raise BaseExceptionGroup("errors during test teardown", exceptions[::-1]) + if nextitem is None: + assert not self.stack + + +def collect_one_node(collector: Collector) -> CollectReport: + ihook = collector.ihook + ihook.pytest_collectstart(collector=collector) + rep: CollectReport = ihook.pytest_make_collect_report(collector=collector) + call = rep.__dict__.pop("call", None) + if call and check_interactive_exception(call, rep): + ihook.pytest_exception_interact(node=collector, call=call, report=rep) + return rep diff --git a/.venv/Lib/site-packages/_pytest/scope.py b/.venv/Lib/site-packages/_pytest/scope.py new file mode 100644 index 0000000..976a3ba --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/scope.py @@ -0,0 +1,91 @@ +""" +Scope definition and related utilities. + +Those are defined here, instead of in the 'fixtures' module because +their use is spread across many other pytest modules, and centralizing it in 'fixtures' +would cause circular references. + +Also this makes the module light to import, as it should. +""" + +from __future__ import annotations + +from enum import Enum +from functools import total_ordering +from typing import Literal + + +_ScopeName = Literal["session", "package", "module", "class", "function"] + + +@total_ordering +class Scope(Enum): + """ + Represents one of the possible fixture scopes in pytest. + + Scopes are ordered from lower to higher, that is: + + ->>> higher ->>> + + Function < Class < Module < Package < Session + + <<<- lower <<<- + """ + + # Scopes need to be listed from lower to higher. + Function: _ScopeName = "function" + Class: _ScopeName = "class" + Module: _ScopeName = "module" + Package: _ScopeName = "package" + Session: _ScopeName = "session" + + def next_lower(self) -> Scope: + """Return the next lower scope.""" + index = _SCOPE_INDICES[self] + if index == 0: + raise ValueError(f"{self} is the lower-most scope") + return _ALL_SCOPES[index - 1] + + def next_higher(self) -> Scope: + """Return the next higher scope.""" + index = _SCOPE_INDICES[self] + if index == len(_SCOPE_INDICES) - 1: + raise ValueError(f"{self} is the upper-most scope") + return _ALL_SCOPES[index + 1] + + def __lt__(self, other: Scope) -> bool: + self_index = _SCOPE_INDICES[self] + other_index = _SCOPE_INDICES[other] + return self_index < other_index + + @classmethod + def from_user( + cls, scope_name: _ScopeName, descr: str, where: str | None = None + ) -> Scope: + """ + Given a scope name from the user, return the equivalent Scope enum. Should be used + whenever we want to convert a user provided scope name to its enum object. + + If the scope name is invalid, construct a user friendly message and call pytest.fail. + """ + from _pytest.outcomes import fail + + try: + # Holding this reference is necessary for mypy at the moment. + scope = Scope(scope_name) + except ValueError: + fail( + "{} {}got an unexpected scope value '{}'".format( + descr, f"from {where} " if where else "", scope_name + ), + pytrace=False, + ) + return scope + + +_ALL_SCOPES = list(Scope) +_SCOPE_INDICES = {scope: index for index, scope in enumerate(_ALL_SCOPES)} + + +# Ordered list of scopes which can contain many tests (in practice all except Function). +HIGH_SCOPES = [x for x in Scope if x is not Scope.Function] diff --git a/.venv/Lib/site-packages/_pytest/setuponly.py b/.venv/Lib/site-packages/_pytest/setuponly.py new file mode 100644 index 0000000..de297f4 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/setuponly.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +from typing import Generator + +from _pytest._io.saferepr import saferepr +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureDef +from _pytest.fixtures import SubRequest +from _pytest.scope import Scope +import pytest + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("debugconfig") + group.addoption( + "--setuponly", + "--setup-only", + action="store_true", + help="Only setup fixtures, do not execute tests", + ) + group.addoption( + "--setupshow", + "--setup-show", + action="store_true", + help="Show setup of fixtures while executing tests", + ) + + +@pytest.hookimpl(wrapper=True) +def pytest_fixture_setup( + fixturedef: FixtureDef[object], request: SubRequest +) -> Generator[None, object, object]: + try: + return (yield) + finally: + if request.config.option.setupshow: + if hasattr(request, "param"): + # Save the fixture parameter so ._show_fixture_action() can + # display it now and during the teardown (in .finish()). + if fixturedef.ids: + if callable(fixturedef.ids): + param = fixturedef.ids(request.param) + else: + param = fixturedef.ids[request.param_index] + else: + param = request.param + fixturedef.cached_param = param # type: ignore[attr-defined] + _show_fixture_action(fixturedef, request.config, "SETUP") + + +def pytest_fixture_post_finalizer( + fixturedef: FixtureDef[object], request: SubRequest +) -> None: + if fixturedef.cached_result is not None: + config = request.config + if config.option.setupshow: + _show_fixture_action(fixturedef, request.config, "TEARDOWN") + if hasattr(fixturedef, "cached_param"): + del fixturedef.cached_param + + +def _show_fixture_action( + fixturedef: FixtureDef[object], config: Config, msg: str +) -> None: + capman = config.pluginmanager.getplugin("capturemanager") + if capman: + capman.suspend_global_capture() + + tw = config.get_terminal_writer() + tw.line() + # Use smaller indentation the higher the scope: Session = 0, Package = 1, etc. + scope_indent = list(reversed(Scope)).index(fixturedef._scope) + tw.write(" " * 2 * scope_indent) + tw.write( + "{step} {scope} {fixture}".format( # noqa: UP032 (Readability) + step=msg.ljust(8), # align the output to TEARDOWN + scope=fixturedef.scope[0].upper(), + fixture=fixturedef.argname, + ) + ) + + if msg == "SETUP": + deps = sorted(arg for arg in fixturedef.argnames if arg != "request") + if deps: + tw.write(" (fixtures used: {})".format(", ".join(deps))) + + if hasattr(fixturedef, "cached_param"): + tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]") + + tw.flush() + + if capman: + capman.resume_global_capture() + + +@pytest.hookimpl(tryfirst=True) +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: + if config.option.setuponly: + config.option.setupshow = True + return None diff --git a/.venv/Lib/site-packages/_pytest/setupplan.py b/.venv/Lib/site-packages/_pytest/setupplan.py new file mode 100644 index 0000000..4e124cc --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/setupplan.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureDef +from _pytest.fixtures import SubRequest +import pytest + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("debugconfig") + group.addoption( + "--setupplan", + "--setup-plan", + action="store_true", + help="Show what fixtures and tests would be executed but " + "don't execute anything", + ) + + +@pytest.hookimpl(tryfirst=True) +def pytest_fixture_setup( + fixturedef: FixtureDef[object], request: SubRequest +) -> object | None: + # Will return a dummy fixture if the setuponly option is provided. + if request.config.option.setupplan: + my_cache_key = fixturedef.cache_key(request) + fixturedef.cached_result = (None, my_cache_key, None) + return fixturedef.cached_result + return None + + +@pytest.hookimpl(tryfirst=True) +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: + if config.option.setupplan: + config.option.setuponly = True + config.option.setupshow = True + return None diff --git a/.venv/Lib/site-packages/_pytest/skipping.py b/.venv/Lib/site-packages/_pytest/skipping.py new file mode 100644 index 0000000..9818be2 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/skipping.py @@ -0,0 +1,301 @@ +# mypy: allow-untyped-defs +"""Support for skip/xfail functions and markers.""" + +from __future__ import annotations + +from collections.abc import Mapping +import dataclasses +import os +import platform +import sys +import traceback +from typing import Generator +from typing import Optional + +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.mark.structures import Mark +from _pytest.nodes import Item +from _pytest.outcomes import fail +from _pytest.outcomes import skip +from _pytest.outcomes import xfail +from _pytest.reports import BaseReport +from _pytest.reports import TestReport +from _pytest.runner import CallInfo +from _pytest.stash import StashKey + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("general") + group.addoption( + "--runxfail", + action="store_true", + dest="runxfail", + default=False, + help="Report the results of xfail tests as if they were not marked", + ) + + parser.addini( + "xfail_strict", + "Default for the strict parameter of xfail " + "markers when not given explicitly (default: False)", + default=False, + type="bool", + ) + + +def pytest_configure(config: Config) -> None: + if config.option.runxfail: + # yay a hack + import pytest + + old = pytest.xfail + config.add_cleanup(lambda: setattr(pytest, "xfail", old)) + + def nop(*args, **kwargs): + pass + + nop.Exception = xfail.Exception # type: ignore[attr-defined] + setattr(pytest, "xfail", nop) + + config.addinivalue_line( + "markers", + "skip(reason=None): skip the given test function with an optional reason. " + 'Example: skip(reason="no way of currently testing this") skips the ' + "test.", + ) + config.addinivalue_line( + "markers", + "skipif(condition, ..., *, reason=...): " + "skip the given test function if any of the conditions evaluate to True. " + "Example: skipif(sys.platform == 'win32') skips the test if we are on the win32 platform. " + "See https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-skipif", + ) + config.addinivalue_line( + "markers", + "xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): " + "mark the test function as an expected failure if any of the conditions " + "evaluate to True. Optionally specify a reason for better reporting " + "and run=False if you don't even want to execute the test function. " + "If only specific exception(s) are expected, you can list them in " + "raises, and if the test fails in other ways, it will be reported as " + "a true failure. See https://docs.pytest.org/en/stable/reference/reference.html#pytest-mark-xfail", + ) + + +def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, str]: + """Evaluate a single skipif/xfail condition. + + If an old-style string condition is given, it is eval()'d, otherwise the + condition is bool()'d. If this fails, an appropriately formatted pytest.fail + is raised. + + Returns (result, reason). The reason is only relevant if the result is True. + """ + # String condition. + if isinstance(condition, str): + globals_ = { + "os": os, + "sys": sys, + "platform": platform, + "config": item.config, + } + for dictionary in reversed( + item.ihook.pytest_markeval_namespace(config=item.config) + ): + if not isinstance(dictionary, Mapping): + raise ValueError( + f"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}" + ) + globals_.update(dictionary) + if hasattr(item, "obj"): + globals_.update(item.obj.__globals__) + try: + filename = f"<{mark.name} condition>" + condition_code = compile(condition, filename, "eval") + result = eval(condition_code, globals_) + except SyntaxError as exc: + msglines = [ + f"Error evaluating {mark.name!r} condition", + " " + condition, + " " + " " * (exc.offset or 0) + "^", + "SyntaxError: invalid syntax", + ] + fail("\n".join(msglines), pytrace=False) + except Exception as exc: + msglines = [ + f"Error evaluating {mark.name!r} condition", + " " + condition, + *traceback.format_exception_only(type(exc), exc), + ] + fail("\n".join(msglines), pytrace=False) + + # Boolean condition. + else: + try: + result = bool(condition) + except Exception as exc: + msglines = [ + f"Error evaluating {mark.name!r} condition as a boolean", + *traceback.format_exception_only(type(exc), exc), + ] + fail("\n".join(msglines), pytrace=False) + + reason = mark.kwargs.get("reason", None) + if reason is None: + if isinstance(condition, str): + reason = "condition: " + condition + else: + # XXX better be checked at collection time + msg = ( + f"Error evaluating {mark.name!r}: " + + "you need to specify reason=STRING when using booleans as conditions." + ) + fail(msg, pytrace=False) + + return result, reason + + +@dataclasses.dataclass(frozen=True) +class Skip: + """The result of evaluate_skip_marks().""" + + reason: str = "unconditional skip" + + +def evaluate_skip_marks(item: Item) -> Skip | None: + """Evaluate skip and skipif marks on item, returning Skip if triggered.""" + for mark in item.iter_markers(name="skipif"): + if "condition" not in mark.kwargs: + conditions = mark.args + else: + conditions = (mark.kwargs["condition"],) + + # Unconditional. + if not conditions: + reason = mark.kwargs.get("reason", "") + return Skip(reason) + + # If any of the conditions are true. + for condition in conditions: + result, reason = evaluate_condition(item, mark, condition) + if result: + return Skip(reason) + + for mark in item.iter_markers(name="skip"): + try: + return Skip(*mark.args, **mark.kwargs) + except TypeError as e: + raise TypeError(str(e) + " - maybe you meant pytest.mark.skipif?") from None + + return None + + +@dataclasses.dataclass(frozen=True) +class Xfail: + """The result of evaluate_xfail_marks().""" + + __slots__ = ("reason", "run", "strict", "raises") + + reason: str + run: bool + strict: bool + raises: tuple[type[BaseException], ...] | None + + +def evaluate_xfail_marks(item: Item) -> Xfail | None: + """Evaluate xfail marks on item, returning Xfail if triggered.""" + for mark in item.iter_markers(name="xfail"): + run = mark.kwargs.get("run", True) + strict = mark.kwargs.get("strict", item.config.getini("xfail_strict")) + raises = mark.kwargs.get("raises", None) + if "condition" not in mark.kwargs: + conditions = mark.args + else: + conditions = (mark.kwargs["condition"],) + + # Unconditional. + if not conditions: + reason = mark.kwargs.get("reason", "") + return Xfail(reason, run, strict, raises) + + # If any of the conditions are true. + for condition in conditions: + result, reason = evaluate_condition(item, mark, condition) + if result: + return Xfail(reason, run, strict, raises) + + return None + + +# Saves the xfail mark evaluation. Can be refreshed during call if None. +xfailed_key = StashKey[Optional[Xfail]]() + + +@hookimpl(tryfirst=True) +def pytest_runtest_setup(item: Item) -> None: + skipped = evaluate_skip_marks(item) + if skipped: + raise skip.Exception(skipped.reason, _use_item_location=True) + + item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) + if xfailed and not item.config.option.runxfail and not xfailed.run: + xfail("[NOTRUN] " + xfailed.reason) + + +@hookimpl(wrapper=True) +def pytest_runtest_call(item: Item) -> Generator[None]: + xfailed = item.stash.get(xfailed_key, None) + if xfailed is None: + item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) + + if xfailed and not item.config.option.runxfail and not xfailed.run: + xfail("[NOTRUN] " + xfailed.reason) + + try: + return (yield) + finally: + # The test run may have added an xfail mark dynamically. + xfailed = item.stash.get(xfailed_key, None) + if xfailed is None: + item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) + + +@hookimpl(wrapper=True) +def pytest_runtest_makereport( + item: Item, call: CallInfo[None] +) -> Generator[None, TestReport, TestReport]: + rep = yield + xfailed = item.stash.get(xfailed_key, None) + if item.config.option.runxfail: + pass # don't interfere + elif call.excinfo and isinstance(call.excinfo.value, xfail.Exception): + assert call.excinfo.value.msg is not None + rep.wasxfail = "reason: " + call.excinfo.value.msg + rep.outcome = "skipped" + elif not rep.skipped and xfailed: + if call.excinfo: + raises = xfailed.raises + if raises is not None and not isinstance(call.excinfo.value, raises): + rep.outcome = "failed" + else: + rep.outcome = "skipped" + rep.wasxfail = xfailed.reason + elif call.when == "call": + if xfailed.strict: + rep.outcome = "failed" + rep.longrepr = "[XPASS(strict)] " + xfailed.reason + else: + rep.outcome = "passed" + rep.wasxfail = xfailed.reason + return rep + + +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: + if hasattr(report, "wasxfail"): + if report.skipped: + return "xfailed", "x", "XFAIL" + elif report.passed: + return "xpassed", "X", "XPASS" + return None diff --git a/.venv/Lib/site-packages/_pytest/stash.py b/.venv/Lib/site-packages/_pytest/stash.py new file mode 100644 index 0000000..6a9ff88 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/stash.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +from typing import Any +from typing import cast +from typing import Generic +from typing import TypeVar + + +__all__ = ["Stash", "StashKey"] + + +T = TypeVar("T") +D = TypeVar("D") + + +class StashKey(Generic[T]): + """``StashKey`` is an object used as a key to a :class:`Stash`. + + A ``StashKey`` is associated with the type ``T`` of the value of the key. + + A ``StashKey`` is unique and cannot conflict with another key. + + .. versionadded:: 7.0 + """ + + __slots__ = () + + +class Stash: + r"""``Stash`` is a type-safe heterogeneous mutable mapping that + allows keys and value types to be defined separately from + where it (the ``Stash``) is created. + + Usually you will be given an object which has a ``Stash``, for example + :class:`~pytest.Config` or a :class:`~_pytest.nodes.Node`: + + .. code-block:: python + + stash: Stash = some_object.stash + + If a module or plugin wants to store data in this ``Stash``, it creates + :class:`StashKey`\s for its keys (at the module level): + + .. code-block:: python + + # At the top-level of the module + some_str_key = StashKey[str]() + some_bool_key = StashKey[bool]() + + To store information: + + .. code-block:: python + + # Value type must match the key. + stash[some_str_key] = "value" + stash[some_bool_key] = True + + To retrieve the information: + + .. code-block:: python + + # The static type of some_str is str. + some_str = stash[some_str_key] + # The static type of some_bool is bool. + some_bool = stash[some_bool_key] + + .. versionadded:: 7.0 + """ + + __slots__ = ("_storage",) + + def __init__(self) -> None: + self._storage: dict[StashKey[Any], object] = {} + + def __setitem__(self, key: StashKey[T], value: T) -> None: + """Set a value for key.""" + self._storage[key] = value + + def __getitem__(self, key: StashKey[T]) -> T: + """Get the value for key. + + Raises ``KeyError`` if the key wasn't set before. + """ + return cast(T, self._storage[key]) + + def get(self, key: StashKey[T], default: D) -> T | D: + """Get the value for key, or return default if the key wasn't set + before.""" + try: + return self[key] + except KeyError: + return default + + def setdefault(self, key: StashKey[T], default: T) -> T: + """Return the value of key if already set, otherwise set the value + of key to default and return default.""" + try: + return self[key] + except KeyError: + self[key] = default + return default + + def __delitem__(self, key: StashKey[T]) -> None: + """Delete the value for key. + + Raises ``KeyError`` if the key wasn't set before. + """ + del self._storage[key] + + def __contains__(self, key: StashKey[T]) -> bool: + """Return whether key was set.""" + return key in self._storage + + def __len__(self) -> int: + """Return how many items exist in the stash.""" + return len(self._storage) diff --git a/.venv/Lib/site-packages/_pytest/stepwise.py b/.venv/Lib/site-packages/_pytest/stepwise.py new file mode 100644 index 0000000..c786080 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/stepwise.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +from _pytest import nodes +from _pytest.cacheprovider import Cache +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.main import Session +from _pytest.reports import TestReport + + +STEPWISE_CACHE_DIR = "cache/stepwise" + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("general") + group.addoption( + "--sw", + "--stepwise", + action="store_true", + default=False, + dest="stepwise", + help="Exit on test failure and continue from last failing test next time", + ) + group.addoption( + "--sw-skip", + "--stepwise-skip", + action="store_true", + default=False, + dest="stepwise_skip", + help="Ignore the first failing test but stop on the next failing test. " + "Implicitly enables --stepwise.", + ) + + +def pytest_configure(config: Config) -> None: + if config.option.stepwise_skip: + # allow --stepwise-skip to work on its own merits. + config.option.stepwise = True + if config.getoption("stepwise"): + config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") + + +def pytest_sessionfinish(session: Session) -> None: + if not session.config.getoption("stepwise"): + assert session.config.cache is not None + if hasattr(session.config, "workerinput"): + # Do not update cache if this process is a xdist worker to prevent + # race conditions (#10641). + return + # Clear the list of failing tests if the plugin is not active. + session.config.cache.set(STEPWISE_CACHE_DIR, []) + + +class StepwisePlugin: + def __init__(self, config: Config) -> None: + self.config = config + self.session: Session | None = None + self.report_status = "" + assert config.cache is not None + self.cache: Cache = config.cache + self.lastfailed: str | None = self.cache.get(STEPWISE_CACHE_DIR, None) + self.skip: bool = config.getoption("stepwise_skip") + + def pytest_sessionstart(self, session: Session) -> None: + self.session = session + + def pytest_collection_modifyitems( + self, config: Config, items: list[nodes.Item] + ) -> None: + if not self.lastfailed: + self.report_status = "no previously failed tests, not skipping." + return + + # check all item nodes until we find a match on last failed + failed_index = None + for index, item in enumerate(items): + if item.nodeid == self.lastfailed: + failed_index = index + break + + # If the previously failed test was not found among the test items, + # do not skip any tests. + if failed_index is None: + self.report_status = "previously failed test not found, not skipping." + else: + self.report_status = f"skipping {failed_index} already passed items." + deselected = items[:failed_index] + del items[:failed_index] + config.hook.pytest_deselected(items=deselected) + + def pytest_runtest_logreport(self, report: TestReport) -> None: + if report.failed: + if self.skip: + # Remove test from the failed ones (if it exists) and unset the skip option + # to make sure the following tests will not be skipped. + if report.nodeid == self.lastfailed: + self.lastfailed = None + + self.skip = False + else: + # Mark test as the last failing and interrupt the test session. + self.lastfailed = report.nodeid + assert self.session is not None + self.session.shouldstop = ( + "Test failed, continuing from this test next run." + ) + + else: + # If the test was actually run and did pass. + if report.when == "call": + # Remove test from the failed ones, if exists. + if report.nodeid == self.lastfailed: + self.lastfailed = None + + def pytest_report_collectionfinish(self) -> str | None: + if self.config.get_verbosity() >= 0 and self.report_status: + return f"stepwise: {self.report_status}" + return None + + def pytest_sessionfinish(self) -> None: + if hasattr(self.config, "workerinput"): + # Do not update cache if this process is a xdist worker to prevent + # race conditions (#10641). + return + self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed) diff --git a/.venv/Lib/site-packages/_pytest/terminal.py b/.venv/Lib/site-packages/_pytest/terminal.py new file mode 100644 index 0000000..ed267bf --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/terminal.py @@ -0,0 +1,1577 @@ +# mypy: allow-untyped-defs +"""Terminal reporting of the full testing process. + +This is a good source for looking at the various reporting hooks. +""" + +from __future__ import annotations + +import argparse +from collections import Counter +import dataclasses +import datetime +from functools import partial +import inspect +from pathlib import Path +import platform +import sys +import textwrap +from typing import Any +from typing import Callable +from typing import ClassVar +from typing import final +from typing import Generator +from typing import Literal +from typing import Mapping +from typing import NamedTuple +from typing import Sequence +from typing import TextIO +from typing import TYPE_CHECKING +import warnings + +import pluggy + +from _pytest import nodes +from _pytest import timing +from _pytest._code import ExceptionInfo +from _pytest._code.code import ExceptionRepr +from _pytest._io import TerminalWriter +from _pytest._io.wcwidth import wcswidth +import _pytest._version +from _pytest.assertion.util import running_on_ci +from _pytest.config import _PluggyPlugin +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.nodes import Item +from _pytest.nodes import Node +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath +from _pytest.reports import BaseReport +from _pytest.reports import CollectReport +from _pytest.reports import TestReport + + +if TYPE_CHECKING: + from _pytest.main import Session + + +REPORT_COLLECTING_RESOLUTION = 0.5 + +KNOWN_TYPES = ( + "failed", + "passed", + "skipped", + "deselected", + "xfailed", + "xpassed", + "warnings", + "error", +) + +_REPORTCHARS_DEFAULT = "fE" + + +class MoreQuietAction(argparse.Action): + """A modified copy of the argparse count action which counts down and updates + the legacy quiet attribute at the same time. + + Used to unify verbosity handling. + """ + + def __init__( + self, + option_strings: Sequence[str], + dest: str, + default: object = None, + required: bool = False, + help: str | None = None, + ) -> None: + super().__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + default=default, + required=required, + help=help, + ) + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: str | Sequence[object] | None, + option_string: str | None = None, + ) -> None: + new_count = getattr(namespace, self.dest, 0) - 1 + setattr(namespace, self.dest, new_count) + # todo Deprecate config.quiet + namespace.quiet = getattr(namespace, "quiet", 0) + 1 + + +class TestShortLogReport(NamedTuple): + """Used to store the test status result category, shortletter and verbose word. + For example ``"rerun", "R", ("RERUN", {"yellow": True})``. + + :ivar category: + The class of result, for example ``“passed”``, ``“skipped”``, ``“error”``, or the empty string. + + :ivar letter: + The short letter shown as testing progresses, for example ``"."``, ``"s"``, ``"E"``, or the empty string. + + :ivar word: + Verbose word is shown as testing progresses in verbose mode, for example ``"PASSED"``, ``"SKIPPED"``, + ``"ERROR"``, or the empty string. + """ + + category: str + letter: str + word: str | tuple[str, Mapping[str, bool]] + + +def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("terminal reporting", "Reporting", after="general") + group._addoption( + "-v", + "--verbose", + action="count", + default=0, + dest="verbose", + help="Increase verbosity", + ) + group._addoption( + "--no-header", + action="store_true", + default=False, + dest="no_header", + help="Disable header", + ) + group._addoption( + "--no-summary", + action="store_true", + default=False, + dest="no_summary", + help="Disable summary", + ) + group._addoption( + "--no-fold-skipped", + action="store_false", + dest="fold_skipped", + default=True, + help="Do not fold skipped tests in short summary.", + ) + group._addoption( + "-q", + "--quiet", + action=MoreQuietAction, + default=0, + dest="verbose", + help="Decrease verbosity", + ) + group._addoption( + "--verbosity", + dest="verbose", + type=int, + default=0, + help="Set verbosity. Default: 0.", + ) + group._addoption( + "-r", + action="store", + dest="reportchars", + default=_REPORTCHARS_DEFAULT, + metavar="chars", + help="Show extra test summary info as specified by chars: (f)ailed, " + "(E)rror, (s)kipped, (x)failed, (X)passed, " + "(p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. " + "(w)arnings are enabled by default (see --disable-warnings), " + "'N' can be used to reset the list. (default: 'fE').", + ) + group._addoption( + "--disable-warnings", + "--disable-pytest-warnings", + default=False, + dest="disable_warnings", + action="store_true", + help="Disable warnings summary", + ) + group._addoption( + "-l", + "--showlocals", + action="store_true", + dest="showlocals", + default=False, + help="Show locals in tracebacks (disabled by default)", + ) + group._addoption( + "--no-showlocals", + action="store_false", + dest="showlocals", + help="Hide locals in tracebacks (negate --showlocals passed through addopts)", + ) + group._addoption( + "--tb", + metavar="style", + action="store", + dest="tbstyle", + default="auto", + choices=["auto", "long", "short", "no", "line", "native"], + help="Traceback print mode (auto/long/short/line/native/no)", + ) + group._addoption( + "--xfail-tb", + action="store_true", + dest="xfail_tb", + default=False, + help="Show tracebacks for xfail (as long as --tb != no)", + ) + group._addoption( + "--show-capture", + action="store", + dest="showcapture", + choices=["no", "stdout", "stderr", "log", "all"], + default="all", + help="Controls how captured stdout/stderr/log is shown on failed tests. " + "Default: all.", + ) + group._addoption( + "--fulltrace", + "--full-trace", + action="store_true", + default=False, + help="Don't cut any tracebacks (default is to cut)", + ) + group._addoption( + "--color", + metavar="color", + action="store", + dest="color", + default="auto", + choices=["yes", "no", "auto"], + help="Color terminal output (yes/no/auto)", + ) + group._addoption( + "--code-highlight", + default="yes", + choices=["yes", "no"], + help="Whether code should be highlighted (only if --color is also enabled). " + "Default: yes.", + ) + + parser.addini( + "console_output_style", + help='Console output: "classic", or with additional progress information ' + '("progress" (percentage) | "count" | "progress-even-when-capture-no" (forces ' + "progress even when capture=no)", + default="progress", + ) + Config._add_verbosity_ini( + parser, + Config.VERBOSITY_TEST_CASES, + help=( + "Specify a verbosity level for test case execution, overriding the main level. " + "Higher levels will provide more detailed information about each test case executed." + ), + ) + + +def pytest_configure(config: Config) -> None: + reporter = TerminalReporter(config, sys.stdout) + config.pluginmanager.register(reporter, "terminalreporter") + if config.option.debug or config.option.traceconfig: + + def mywriter(tags, args): + msg = " ".join(map(str, args)) + reporter.write_line("[traceconfig] " + msg) + + config.trace.root.setprocessor("pytest:config", mywriter) + + +def getreportopt(config: Config) -> str: + reportchars: str = config.option.reportchars + + old_aliases = {"F", "S"} + reportopts = "" + for char in reportchars: + if char in old_aliases: + char = char.lower() + if char == "a": + reportopts = "sxXEf" + elif char == "A": + reportopts = "PpsxXEf" + elif char == "N": + reportopts = "" + elif char not in reportopts: + reportopts += char + + if not config.option.disable_warnings and "w" not in reportopts: + reportopts = "w" + reportopts + elif config.option.disable_warnings and "w" in reportopts: + reportopts = reportopts.replace("w", "") + + return reportopts + + +@hookimpl(trylast=True) # after _pytest.runner +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]: + letter = "F" + if report.passed: + letter = "." + elif report.skipped: + letter = "s" + + outcome: str = report.outcome + if report.when in ("collect", "setup", "teardown") and outcome == "failed": + outcome = "error" + letter = "E" + + return outcome, letter, outcome.upper() + + +@dataclasses.dataclass +class WarningReport: + """Simple structure to hold warnings information captured by ``pytest_warning_recorded``. + + :ivar str message: + User friendly message about the warning. + :ivar str|None nodeid: + nodeid that generated the warning (see ``get_location``). + :ivar tuple fslocation: + File system location of the source of the warning (see ``get_location``). + """ + + message: str + nodeid: str | None = None + fslocation: tuple[str, int] | None = None + + count_towards_summary: ClassVar = True + + def get_location(self, config: Config) -> str | None: + """Return the more user-friendly information about the location of a warning, or None.""" + if self.nodeid: + return self.nodeid + if self.fslocation: + filename, linenum = self.fslocation + relpath = bestrelpath(config.invocation_params.dir, absolutepath(filename)) + return f"{relpath}:{linenum}" + return None + + +@final +class TerminalReporter: + def __init__(self, config: Config, file: TextIO | None = None) -> None: + import _pytest.config + + self.config = config + self._numcollected = 0 + self._session: Session | None = None + self._showfspath: bool | None = None + + self.stats: dict[str, list[Any]] = {} + self._main_color: str | None = None + self._known_types: list[str] | None = None + self.startpath = config.invocation_params.dir + if file is None: + file = sys.stdout + self._tw = _pytest.config.create_terminal_writer(config, file) + self._screen_width = self._tw.fullwidth + self.currentfspath: None | Path | str | int = None + self.reportchars = getreportopt(config) + self.foldskipped = config.option.fold_skipped + self.hasmarkup = self._tw.hasmarkup + self.isatty = file.isatty() + self._progress_nodeids_reported: set[str] = set() + self._show_progress_info = self._determine_show_progress_info() + self._collect_report_last_write: float | None = None + self._already_displayed_warnings: int | None = None + self._keyboardinterrupt_memo: ExceptionRepr | None = None + + def _determine_show_progress_info(self) -> Literal["progress", "count", False]: + """Return whether we should display progress information based on the current config.""" + # do not show progress if we are not capturing output (#3038) unless explicitly + # overridden by progress-even-when-capture-no + if ( + self.config.getoption("capture", "no") == "no" + and self.config.getini("console_output_style") + != "progress-even-when-capture-no" + ): + return False + # do not show progress if we are showing fixture setup/teardown + if self.config.getoption("setupshow", False): + return False + cfg: str = self.config.getini("console_output_style") + if cfg in {"progress", "progress-even-when-capture-no"}: + return "progress" + elif cfg == "count": + return "count" + else: + return False + + @property + def verbosity(self) -> int: + verbosity: int = self.config.option.verbose + return verbosity + + @property + def showheader(self) -> bool: + return self.verbosity >= 0 + + @property + def no_header(self) -> bool: + return bool(self.config.option.no_header) + + @property + def no_summary(self) -> bool: + return bool(self.config.option.no_summary) + + @property + def showfspath(self) -> bool: + if self._showfspath is None: + return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) >= 0 + return self._showfspath + + @showfspath.setter + def showfspath(self, value: bool | None) -> None: + self._showfspath = value + + @property + def showlongtestinfo(self) -> bool: + return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) > 0 + + def hasopt(self, char: str) -> bool: + char = {"xfailed": "x", "skipped": "s"}.get(char, char) + return char in self.reportchars + + def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None: + fspath = self.config.rootpath / nodeid.split("::")[0] + if self.currentfspath is None or fspath != self.currentfspath: + if self.currentfspath is not None and self._show_progress_info: + self._write_progress_information_filling_space() + self.currentfspath = fspath + relfspath = bestrelpath(self.startpath, fspath) + self._tw.line() + self._tw.write(relfspath + " ") + self._tw.write(res, flush=True, **markup) + + def write_ensure_prefix(self, prefix: str, extra: str = "", **kwargs) -> None: + if self.currentfspath != prefix: + self._tw.line() + self.currentfspath = prefix + self._tw.write(prefix) + if extra: + self._tw.write(extra, **kwargs) + self.currentfspath = -2 + + def ensure_newline(self) -> None: + if self.currentfspath: + self._tw.line() + self.currentfspath = None + + def wrap_write( + self, + content: str, + *, + flush: bool = False, + margin: int = 8, + line_sep: str = "\n", + **markup: bool, + ) -> None: + """Wrap message with margin for progress info.""" + width_of_current_line = self._tw.width_of_current_line + wrapped = line_sep.join( + textwrap.wrap( + " " * width_of_current_line + content, + width=self._screen_width - margin, + drop_whitespace=True, + replace_whitespace=False, + ), + ) + wrapped = wrapped[width_of_current_line:] + self._tw.write(wrapped, flush=flush, **markup) + + def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: + self._tw.write(content, flush=flush, **markup) + + def flush(self) -> None: + self._tw.flush() + + def write_line(self, line: str | bytes, **markup: bool) -> None: + if not isinstance(line, str): + line = str(line, errors="replace") + self.ensure_newline() + self._tw.line(line, **markup) + + def rewrite(self, line: str, **markup: bool) -> None: + """Rewinds the terminal cursor to the beginning and writes the given line. + + :param erase: + If True, will also add spaces until the full terminal width to ensure + previous lines are properly erased. + + The rest of the keyword arguments are markup instructions. + """ + erase = markup.pop("erase", False) + if erase: + fill_count = self._tw.fullwidth - len(line) - 1 + fill = " " * fill_count + else: + fill = "" + line = str(line) + self._tw.write("\r" + line + fill, **markup) + + def write_sep( + self, + sep: str, + title: str | None = None, + fullwidth: int | None = None, + **markup: bool, + ) -> None: + self.ensure_newline() + self._tw.sep(sep, title, fullwidth, **markup) + + def section(self, title: str, sep: str = "=", **kw: bool) -> None: + self._tw.sep(sep, title, **kw) + + def line(self, msg: str, **kw: bool) -> None: + self._tw.line(msg, **kw) + + def _add_stats(self, category: str, items: Sequence[Any]) -> None: + set_main_color = category not in self.stats + self.stats.setdefault(category, []).extend(items) + if set_main_color: + self._set_main_color() + + def pytest_internalerror(self, excrepr: ExceptionRepr) -> bool: + for line in str(excrepr).split("\n"): + self.write_line("INTERNALERROR> " + line) + return True + + def pytest_warning_recorded( + self, + warning_message: warnings.WarningMessage, + nodeid: str, + ) -> None: + from _pytest.warnings import warning_record_to_str + + fslocation = warning_message.filename, warning_message.lineno + message = warning_record_to_str(warning_message) + + warning_report = WarningReport( + fslocation=fslocation, message=message, nodeid=nodeid + ) + self._add_stats("warnings", [warning_report]) + + def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: + if self.config.option.traceconfig: + msg = f"PLUGIN registered: {plugin}" + # XXX This event may happen during setup/teardown time + # which unfortunately captures our output here + # which garbles our output if we use self.write_line. + self.write_line(msg) + + def pytest_deselected(self, items: Sequence[Item]) -> None: + self._add_stats("deselected", items) + + def pytest_runtest_logstart( + self, nodeid: str, location: tuple[str, int | None, str] + ) -> None: + fspath, lineno, domain = location + # Ensure that the path is printed before the + # 1st test of a module starts running. + if self.showlongtestinfo: + line = self._locationline(nodeid, fspath, lineno, domain) + self.write_ensure_prefix(line, "") + self.flush() + elif self.showfspath: + self.write_fspath_result(nodeid, "") + self.flush() + + def pytest_runtest_logreport(self, report: TestReport) -> None: + self._tests_ran = True + rep = report + + res = TestShortLogReport( + *self.config.hook.pytest_report_teststatus(report=rep, config=self.config) + ) + category, letter, word = res.category, res.letter, res.word + if not isinstance(word, tuple): + markup = None + else: + word, markup = word + self._add_stats(category, [rep]) + if not letter and not word: + # Probably passed setup/teardown. + return + if markup is None: + was_xfail = hasattr(report, "wasxfail") + if rep.passed and not was_xfail: + markup = {"green": True} + elif rep.passed and was_xfail: + markup = {"yellow": True} + elif rep.failed: + markup = {"red": True} + elif rep.skipped: + markup = {"yellow": True} + else: + markup = {} + self._progress_nodeids_reported.add(rep.nodeid) + if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0: + self._tw.write(letter, **markup) + # When running in xdist, the logreport and logfinish of multiple + # items are interspersed, e.g. `logreport`, `logreport`, + # `logfinish`, `logfinish`. To avoid the "past edge" calculation + # from getting confused and overflowing (#7166), do the past edge + # printing here and not in logfinish, except for the 100% which + # should only be printed after all teardowns are finished. + if self._show_progress_info and not self._is_last_item: + self._write_progress_information_if_past_edge() + else: + line = self._locationline(rep.nodeid, *rep.location) + running_xdist = hasattr(rep, "node") + if not running_xdist: + self.write_ensure_prefix(line, word, **markup) + if rep.skipped or hasattr(report, "wasxfail"): + reason = _get_raw_skip_reason(rep) + if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) < 2: + available_width = ( + (self._tw.fullwidth - self._tw.width_of_current_line) + - len(" [100%]") + - 1 + ) + formatted_reason = _format_trimmed( + " ({})", reason, available_width + ) + else: + formatted_reason = f" ({reason})" + + if reason and formatted_reason is not None: + self.wrap_write(formatted_reason) + if self._show_progress_info: + self._write_progress_information_filling_space() + else: + self.ensure_newline() + self._tw.write(f"[{rep.node.gateway.id}]") + if self._show_progress_info: + self._tw.write( + self._get_progress_information_message() + " ", cyan=True + ) + else: + self._tw.write(" ") + self._tw.write(word, **markup) + self._tw.write(" " + line) + self.currentfspath = -2 + self.flush() + + @property + def _is_last_item(self) -> bool: + assert self._session is not None + return len(self._progress_nodeids_reported) == self._session.testscollected + + @hookimpl(wrapper=True) + def pytest_runtestloop(self) -> Generator[None, object, object]: + result = yield + + # Write the final/100% progress -- deferred until the loop is complete. + if ( + self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0 + and self._show_progress_info + and self._progress_nodeids_reported + ): + self._write_progress_information_filling_space() + + return result + + def _get_progress_information_message(self) -> str: + assert self._session + collected = self._session.testscollected + if self._show_progress_info == "count": + if collected: + progress = len(self._progress_nodeids_reported) + counter_format = f"{{:{len(str(collected))}d}}" + format_string = f" [{counter_format}/{{}}]" + return format_string.format(progress, collected) + return f" [ {collected} / {collected} ]" + else: + if collected: + return ( + f" [{len(self._progress_nodeids_reported) * 100 // collected:3d}%]" + ) + return " [100%]" + + def _write_progress_information_if_past_edge(self) -> None: + w = self._width_of_current_line + if self._show_progress_info == "count": + assert self._session + num_tests = self._session.testscollected + progress_length = len(f" [{num_tests}/{num_tests}]") + else: + progress_length = len(" [100%]") + past_edge = w + progress_length + 1 >= self._screen_width + if past_edge: + main_color, _ = self._get_main_color() + msg = self._get_progress_information_message() + self._tw.write(msg + "\n", **{main_color: True}) + + def _write_progress_information_filling_space(self) -> None: + color, _ = self._get_main_color() + msg = self._get_progress_information_message() + w = self._width_of_current_line + fill = self._tw.fullwidth - w - 1 + self.write(msg.rjust(fill), flush=True, **{color: True}) + + @property + def _width_of_current_line(self) -> int: + """Return the width of the current line.""" + return self._tw.width_of_current_line + + def pytest_collection(self) -> None: + if self.isatty: + if self.config.option.verbose >= 0: + self.write("collecting ... ", flush=True, bold=True) + self._collect_report_last_write = timing.time() + elif self.config.option.verbose >= 1: + self.write("collecting ... ", flush=True, bold=True) + + def pytest_collectreport(self, report: CollectReport) -> None: + if report.failed: + self._add_stats("error", [report]) + elif report.skipped: + self._add_stats("skipped", [report]) + items = [x for x in report.result if isinstance(x, Item)] + self._numcollected += len(items) + if self.isatty: + self.report_collect() + + def report_collect(self, final: bool = False) -> None: + if self.config.option.verbose < 0: + return + + if not final: + # Only write "collecting" report every 0.5s. + t = timing.time() + if ( + self._collect_report_last_write is not None + and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION + ): + return + self._collect_report_last_write = t + + errors = len(self.stats.get("error", [])) + skipped = len(self.stats.get("skipped", [])) + deselected = len(self.stats.get("deselected", [])) + selected = self._numcollected - deselected + line = "collected " if final else "collecting " + line += ( + str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s") + ) + if errors: + line += " / %d error%s" % (errors, "s" if errors != 1 else "") + if deselected: + line += " / %d deselected" % deselected + if skipped: + line += " / %d skipped" % skipped + if self._numcollected > selected: + line += " / %d selected" % selected + if self.isatty: + self.rewrite(line, bold=True, erase=True) + if final: + self.write("\n") + else: + self.write_line(line) + + @hookimpl(trylast=True) + def pytest_sessionstart(self, session: Session) -> None: + self._session = session + self._sessionstarttime = timing.time() + if not self.showheader: + return + self.write_sep("=", "test session starts", bold=True) + verinfo = platform.python_version() + if not self.no_header: + msg = f"platform {sys.platform} -- Python {verinfo}" + pypy_version_info = getattr(sys, "pypy_version_info", None) + if pypy_version_info: + verinfo = ".".join(map(str, pypy_version_info[:3])) + msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]" + msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}" + if ( + self.verbosity > 0 + or self.config.option.debug + or getattr(self.config.option, "pastebin", None) + ): + msg += " -- " + str(sys.executable) + self.write_line(msg) + lines = self.config.hook.pytest_report_header( + config=self.config, start_path=self.startpath + ) + self._write_report_lines_from_hooks(lines) + + def _write_report_lines_from_hooks( + self, lines: Sequence[str | Sequence[str]] + ) -> None: + for line_or_lines in reversed(lines): + if isinstance(line_or_lines, str): + self.write_line(line_or_lines) + else: + for line in line_or_lines: + self.write_line(line) + + def pytest_report_header(self, config: Config) -> list[str]: + result = [f"rootdir: {config.rootpath}"] + + if config.inipath: + result.append("configfile: " + bestrelpath(config.rootpath, config.inipath)) + + if config.args_source == Config.ArgsSource.TESTPATHS: + testpaths: list[str] = config.getini("testpaths") + result.append("testpaths: {}".format(", ".join(testpaths))) + + plugininfo = config.pluginmanager.list_plugin_distinfo() + if plugininfo: + result.append( + "plugins: {}".format(", ".join(_plugin_nameversions(plugininfo))) + ) + return result + + def pytest_collection_finish(self, session: Session) -> None: + self.report_collect(True) + + lines = self.config.hook.pytest_report_collectionfinish( + config=self.config, + start_path=self.startpath, + items=session.items, + ) + self._write_report_lines_from_hooks(lines) + + if self.config.getoption("collectonly"): + if session.items: + if self.config.option.verbose > -1: + self._tw.line("") + self._printcollecteditems(session.items) + + failed = self.stats.get("failed") + if failed: + self._tw.sep("!", "collection failures") + for rep in failed: + rep.toterminal(self._tw) + + def _printcollecteditems(self, items: Sequence[Item]) -> None: + test_cases_verbosity = self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) + if test_cases_verbosity < 0: + if test_cases_verbosity < -1: + counts = Counter(item.nodeid.split("::", 1)[0] for item in items) + for name, count in sorted(counts.items()): + self._tw.line("%s: %d" % (name, count)) + else: + for item in items: + self._tw.line(item.nodeid) + return + stack: list[Node] = [] + indent = "" + for item in items: + needed_collectors = item.listchain()[1:] # strip root node + while stack: + if stack == needed_collectors[: len(stack)]: + break + stack.pop() + for col in needed_collectors[len(stack) :]: + stack.append(col) + indent = (len(stack) - 1) * " " + self._tw.line(f"{indent}{col}") + if test_cases_verbosity >= 1: + obj = getattr(col, "obj", None) + doc = inspect.getdoc(obj) if obj else None + if doc: + for line in doc.splitlines(): + self._tw.line("{}{}".format(indent + " ", line)) + + @hookimpl(wrapper=True) + def pytest_sessionfinish( + self, session: Session, exitstatus: int | ExitCode + ) -> Generator[None]: + result = yield + self._tw.line("") + summary_exit_codes = ( + ExitCode.OK, + ExitCode.TESTS_FAILED, + ExitCode.INTERRUPTED, + ExitCode.USAGE_ERROR, + ExitCode.NO_TESTS_COLLECTED, + ) + if exitstatus in summary_exit_codes and not self.no_summary: + self.config.hook.pytest_terminal_summary( + terminalreporter=self, exitstatus=exitstatus, config=self.config + ) + if session.shouldfail: + self.write_sep("!", str(session.shouldfail), red=True) + if exitstatus == ExitCode.INTERRUPTED: + self._report_keyboardinterrupt() + self._keyboardinterrupt_memo = None + elif session.shouldstop: + self.write_sep("!", str(session.shouldstop), red=True) + self.summary_stats() + return result + + @hookimpl(wrapper=True) + def pytest_terminal_summary(self) -> Generator[None]: + self.summary_errors() + self.summary_failures() + self.summary_xfailures() + self.summary_warnings() + self.summary_passes() + self.summary_xpasses() + try: + return (yield) + finally: + self.short_test_summary() + # Display any extra warnings from teardown here (if any). + self.summary_warnings() + + def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None: + self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) + + def pytest_unconfigure(self) -> None: + if self._keyboardinterrupt_memo is not None: + self._report_keyboardinterrupt() + + def _report_keyboardinterrupt(self) -> None: + excrepr = self._keyboardinterrupt_memo + assert excrepr is not None + assert excrepr.reprcrash is not None + msg = excrepr.reprcrash.message + self.write_sep("!", msg) + if "KeyboardInterrupt" in msg: + if self.config.option.fulltrace: + excrepr.toterminal(self._tw) + else: + excrepr.reprcrash.toterminal(self._tw) + self._tw.line( + "(to show a full traceback on KeyboardInterrupt use --full-trace)", + yellow=True, + ) + + def _locationline( + self, nodeid: str, fspath: str, lineno: int | None, domain: str + ) -> str: + def mkrel(nodeid: str) -> str: + line = self.config.cwd_relative_nodeid(nodeid) + if domain and line.endswith(domain): + line = line[: -len(domain)] + values = domain.split("[") + values[0] = values[0].replace(".", "::") # don't replace '.' in params + line += "[".join(values) + return line + + # fspath comes from testid which has a "/"-normalized path. + if fspath: + res = mkrel(nodeid) + if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace( + "\\", nodes.SEP + ): + res += " <- " + bestrelpath(self.startpath, Path(fspath)) + else: + res = "[location]" + return res + " " + + def _getfailureheadline(self, rep): + head_line = rep.head_line + if head_line: + return head_line + return "test session" # XXX? + + def _getcrashline(self, rep): + try: + return str(rep.longrepr.reprcrash) + except AttributeError: + try: + return str(rep.longrepr)[:50] + except AttributeError: + return "" + + # + # Summaries for sessionfinish. + # + def getreports(self, name: str): + return [x for x in self.stats.get(name, ()) if not hasattr(x, "_pdbshown")] + + def summary_warnings(self) -> None: + if self.hasopt("w"): + all_warnings: list[WarningReport] | None = self.stats.get("warnings") + if not all_warnings: + return + + final = self._already_displayed_warnings is not None + if final: + warning_reports = all_warnings[self._already_displayed_warnings :] + else: + warning_reports = all_warnings + self._already_displayed_warnings = len(warning_reports) + if not warning_reports: + return + + reports_grouped_by_message: dict[str, list[WarningReport]] = {} + for wr in warning_reports: + reports_grouped_by_message.setdefault(wr.message, []).append(wr) + + def collapsed_location_report(reports: list[WarningReport]) -> str: + locations = [] + for w in reports: + location = w.get_location(self.config) + if location: + locations.append(location) + + if len(locations) < 10: + return "\n".join(map(str, locations)) + + counts_by_filename = Counter( + str(loc).split("::", 1)[0] for loc in locations + ) + return "\n".join( + "{}: {} warning{}".format(k, v, "s" if v > 1 else "") + for k, v in counts_by_filename.items() + ) + + title = "warnings summary (final)" if final else "warnings summary" + self.write_sep("=", title, yellow=True, bold=False) + for message, message_reports in reports_grouped_by_message.items(): + maybe_location = collapsed_location_report(message_reports) + if maybe_location: + self._tw.line(maybe_location) + lines = message.splitlines() + indented = "\n".join(" " + x for x in lines) + message = indented.rstrip() + else: + message = message.rstrip() + self._tw.line(message) + self._tw.line() + self._tw.line( + "-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html" + ) + + def summary_passes(self) -> None: + self.summary_passes_combined("passed", "PASSES", "P") + + def summary_xpasses(self) -> None: + self.summary_passes_combined("xpassed", "XPASSES", "X") + + def summary_passes_combined( + self, which_reports: str, sep_title: str, needed_opt: str + ) -> None: + if self.config.option.tbstyle != "no": + if self.hasopt(needed_opt): + reports: list[TestReport] = self.getreports(which_reports) + if not reports: + return + self.write_sep("=", sep_title) + for rep in reports: + if rep.sections: + msg = self._getfailureheadline(rep) + self.write_sep("_", msg, green=True, bold=True) + self._outrep_summary(rep) + self._handle_teardown_sections(rep.nodeid) + + def _get_teardown_reports(self, nodeid: str) -> list[TestReport]: + reports = self.getreports("") + return [ + report + for report in reports + if report.when == "teardown" and report.nodeid == nodeid + ] + + def _handle_teardown_sections(self, nodeid: str) -> None: + for report in self._get_teardown_reports(nodeid): + self.print_teardown_sections(report) + + def print_teardown_sections(self, rep: TestReport) -> None: + showcapture = self.config.option.showcapture + if showcapture == "no": + return + for secname, content in rep.sections: + if showcapture != "all" and showcapture not in secname: + continue + if "teardown" in secname: + self._tw.sep("-", secname) + if content[-1:] == "\n": + content = content[:-1] + self._tw.line(content) + + def summary_failures(self) -> None: + style = self.config.option.tbstyle + self.summary_failures_combined("failed", "FAILURES", style=style) + + def summary_xfailures(self) -> None: + show_tb = self.config.option.xfail_tb + style = self.config.option.tbstyle if show_tb else "no" + self.summary_failures_combined("xfailed", "XFAILURES", style=style) + + def summary_failures_combined( + self, + which_reports: str, + sep_title: str, + *, + style: str, + needed_opt: str | None = None, + ) -> None: + if style != "no": + if not needed_opt or self.hasopt(needed_opt): + reports: list[BaseReport] = self.getreports(which_reports) + if not reports: + return + self.write_sep("=", sep_title) + if style == "line": + for rep in reports: + line = self._getcrashline(rep) + self.write_line(line) + else: + for rep in reports: + msg = self._getfailureheadline(rep) + self.write_sep("_", msg, red=True, bold=True) + self._outrep_summary(rep) + self._handle_teardown_sections(rep.nodeid) + + def summary_errors(self) -> None: + if self.config.option.tbstyle != "no": + reports: list[BaseReport] = self.getreports("error") + if not reports: + return + self.write_sep("=", "ERRORS") + for rep in self.stats["error"]: + msg = self._getfailureheadline(rep) + if rep.when == "collect": + msg = "ERROR collecting " + msg + else: + msg = f"ERROR at {rep.when} of {msg}" + self.write_sep("_", msg, red=True, bold=True) + self._outrep_summary(rep) + + def _outrep_summary(self, rep: BaseReport) -> None: + rep.toterminal(self._tw) + showcapture = self.config.option.showcapture + if showcapture == "no": + return + for secname, content in rep.sections: + if showcapture != "all" and showcapture not in secname: + continue + self._tw.sep("-", secname) + if content[-1:] == "\n": + content = content[:-1] + self._tw.line(content) + + def summary_stats(self) -> None: + if self.verbosity < -1: + return + + session_duration = timing.time() - self._sessionstarttime + (parts, main_color) = self.build_summary_stats_line() + line_parts = [] + + display_sep = self.verbosity >= 0 + if display_sep: + fullwidth = self._tw.fullwidth + for text, markup in parts: + with_markup = self._tw.markup(text, **markup) + if display_sep: + fullwidth += len(with_markup) - len(text) + line_parts.append(with_markup) + msg = ", ".join(line_parts) + + main_markup = {main_color: True} + duration = f" in {format_session_duration(session_duration)}" + duration_with_markup = self._tw.markup(duration, **main_markup) + if display_sep: + fullwidth += len(duration_with_markup) - len(duration) + msg += duration_with_markup + + if display_sep: + markup_for_end_sep = self._tw.markup("", **main_markup) + if markup_for_end_sep.endswith("\x1b[0m"): + markup_for_end_sep = markup_for_end_sep[:-4] + fullwidth += len(markup_for_end_sep) + msg += markup_for_end_sep + + if display_sep: + self.write_sep("=", msg, fullwidth=fullwidth, **main_markup) + else: + self.write_line(msg, **main_markup) + + def short_test_summary(self) -> None: + if not self.reportchars: + return + + def show_simple(lines: list[str], *, stat: str) -> None: + failed = self.stats.get(stat, []) + if not failed: + return + config = self.config + for rep in failed: + color = _color_for_type.get(stat, _color_for_type_default) + line = _get_line_with_reprcrash_message( + config, rep, self._tw, {color: True} + ) + lines.append(line) + + def show_xfailed(lines: list[str]) -> None: + xfailed = self.stats.get("xfailed", []) + for rep in xfailed: + verbose_word, verbose_markup = rep._get_verbose_word_with_markup( + self.config, {_color_for_type["warnings"]: True} + ) + markup_word = self._tw.markup(verbose_word, **verbose_markup) + nodeid = _get_node_id_with_markup(self._tw, self.config, rep) + line = f"{markup_word} {nodeid}" + reason = rep.wasxfail + if reason: + line += " - " + str(reason) + + lines.append(line) + + def show_xpassed(lines: list[str]) -> None: + xpassed = self.stats.get("xpassed", []) + for rep in xpassed: + verbose_word, verbose_markup = rep._get_verbose_word_with_markup( + self.config, {_color_for_type["warnings"]: True} + ) + markup_word = self._tw.markup(verbose_word, **verbose_markup) + nodeid = _get_node_id_with_markup(self._tw, self.config, rep) + line = f"{markup_word} {nodeid}" + reason = rep.wasxfail + if reason: + line += " - " + str(reason) + lines.append(line) + + def show_skipped_folded(lines: list[str]) -> None: + skipped: list[CollectReport] = self.stats.get("skipped", []) + fskips = _folded_skips(self.startpath, skipped) if skipped else [] + if not fskips: + return + verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup( + self.config, {_color_for_type["warnings"]: True} + ) + markup_word = self._tw.markup(verbose_word, **verbose_markup) + prefix = "Skipped: " + for num, fspath, lineno, reason in fskips: + if reason.startswith(prefix): + reason = reason[len(prefix) :] + if lineno is not None: + lines.append( + "%s [%d] %s:%d: %s" % (markup_word, num, fspath, lineno, reason) + ) + else: + lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) + + def show_skipped_unfolded(lines: list[str]) -> None: + skipped: list[CollectReport] = self.stats.get("skipped", []) + + for rep in skipped: + assert rep.longrepr is not None + assert isinstance(rep.longrepr, tuple), (rep, rep.longrepr) + assert len(rep.longrepr) == 3, (rep, rep.longrepr) + + verbose_word, verbose_markup = rep._get_verbose_word_with_markup( + self.config, {_color_for_type["warnings"]: True} + ) + markup_word = self._tw.markup(verbose_word, **verbose_markup) + nodeid = _get_node_id_with_markup(self._tw, self.config, rep) + line = f"{markup_word} {nodeid}" + reason = rep.longrepr[2] + if reason: + line += " - " + str(reason) + lines.append(line) + + def show_skipped(lines: list[str]) -> None: + if self.foldskipped: + show_skipped_folded(lines) + else: + show_skipped_unfolded(lines) + + REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = { + "x": show_xfailed, + "X": show_xpassed, + "f": partial(show_simple, stat="failed"), + "s": show_skipped, + "p": partial(show_simple, stat="passed"), + "E": partial(show_simple, stat="error"), + } + + lines: list[str] = [] + for char in self.reportchars: + action = REPORTCHAR_ACTIONS.get(char) + if action: # skipping e.g. "P" (passed with output) here. + action(lines) + + if lines: + self.write_sep("=", "short test summary info", cyan=True, bold=True) + for line in lines: + self.write_line(line) + + def _get_main_color(self) -> tuple[str, list[str]]: + if self._main_color is None or self._known_types is None or self._is_last_item: + self._set_main_color() + assert self._main_color + assert self._known_types + return self._main_color, self._known_types + + def _determine_main_color(self, unknown_type_seen: bool) -> str: + stats = self.stats + if "failed" in stats or "error" in stats: + main_color = "red" + elif "warnings" in stats or "xpassed" in stats or unknown_type_seen: + main_color = "yellow" + elif "passed" in stats or not self._is_last_item: + main_color = "green" + else: + main_color = "yellow" + return main_color + + def _set_main_color(self) -> None: + unknown_types: list[str] = [] + for found_type in self.stats: + if found_type: # setup/teardown reports have an empty key, ignore them + if found_type not in KNOWN_TYPES and found_type not in unknown_types: + unknown_types.append(found_type) + self._known_types = list(KNOWN_TYPES) + unknown_types + self._main_color = self._determine_main_color(bool(unknown_types)) + + def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]: + """ + Build the parts used in the last summary stats line. + + The summary stats line is the line shown at the end, "=== 12 passed, 2 errors in Xs===". + + This function builds a list of the "parts" that make up for the text in that line, in + the example above it would be: + + [ + ("12 passed", {"green": True}), + ("2 errors", {"red": True} + ] + + That last dict for each line is a "markup dictionary", used by TerminalWriter to + color output. + + The final color of the line is also determined by this function, and is the second + element of the returned tuple. + """ + if self.config.getoption("collectonly"): + return self._build_collect_only_summary_stats_line() + else: + return self._build_normal_summary_stats_line() + + def _get_reports_to_display(self, key: str) -> list[Any]: + """Get test/collection reports for the given status key, such as `passed` or `error`.""" + reports = self.stats.get(key, []) + return [x for x in reports if getattr(x, "count_towards_summary", True)] + + def _build_normal_summary_stats_line( + self, + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: + main_color, known_types = self._get_main_color() + parts = [] + + for key in known_types: + reports = self._get_reports_to_display(key) + if reports: + count = len(reports) + color = _color_for_type.get(key, _color_for_type_default) + markup = {color: True, "bold": color == main_color} + parts.append(("%d %s" % pluralize(count, key), markup)) + + if not parts: + parts = [("no tests ran", {_color_for_type_default: True})] + + return parts, main_color + + def _build_collect_only_summary_stats_line( + self, + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: + deselected = len(self._get_reports_to_display("deselected")) + errors = len(self._get_reports_to_display("error")) + + if self._numcollected == 0: + parts = [("no tests collected", {"yellow": True})] + main_color = "yellow" + + elif deselected == 0: + main_color = "green" + collected_output = "%d %s collected" % pluralize(self._numcollected, "test") + parts = [(collected_output, {main_color: True})] + else: + all_tests_were_deselected = self._numcollected == deselected + if all_tests_were_deselected: + main_color = "yellow" + collected_output = f"no tests collected ({deselected} deselected)" + else: + main_color = "green" + selected = self._numcollected - deselected + collected_output = f"{selected}/{self._numcollected} tests collected ({deselected} deselected)" + + parts = [(collected_output, {main_color: True})] + + if errors: + main_color = _color_for_type["error"] + parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})] + + return parts, main_color + + +def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport): + nodeid = config.cwd_relative_nodeid(rep.nodeid) + path, *parts = nodeid.split("::") + if parts: + parts_markup = tw.markup("::".join(parts), bold=True) + return path + "::" + parts_markup + else: + return path + + +def _format_trimmed(format: str, msg: str, available_width: int) -> str | None: + """Format msg into format, ellipsizing it if doesn't fit in available_width. + + Returns None if even the ellipsis can't fit. + """ + # Only use the first line. + i = msg.find("\n") + if i != -1: + msg = msg[:i] + + ellipsis = "..." + format_width = wcswidth(format.format("")) + if format_width + len(ellipsis) > available_width: + return None + + if format_width + wcswidth(msg) > available_width: + available_width -= len(ellipsis) + msg = msg[:available_width] + while format_width + wcswidth(msg) > available_width: + msg = msg[:-1] + msg += ellipsis + + return format.format(msg) + + +def _get_line_with_reprcrash_message( + config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool] +) -> str: + """Get summary line for a report, trying to add reprcrash message.""" + verbose_word, verbose_markup = rep._get_verbose_word_with_markup( + config, word_markup + ) + word = tw.markup(verbose_word, **verbose_markup) + node = _get_node_id_with_markup(tw, config, rep) + + line = f"{word} {node}" + line_width = wcswidth(line) + + try: + # Type ignored intentionally -- possible AttributeError expected. + msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] + except AttributeError: + pass + else: + if running_on_ci() or config.option.verbose >= 2: + msg = f" - {msg}" + else: + available_width = tw.fullwidth - line_width + msg = _format_trimmed(" - {}", msg, available_width) + if msg is not None: + line += msg + + return line + + +def _folded_skips( + startpath: Path, + skipped: Sequence[CollectReport], +) -> list[tuple[int, str, int | None, str]]: + d: dict[tuple[str, int | None, str], list[CollectReport]] = {} + for event in skipped: + assert event.longrepr is not None + assert isinstance(event.longrepr, tuple), (event, event.longrepr) + assert len(event.longrepr) == 3, (event, event.longrepr) + fspath, lineno, reason = event.longrepr + # For consistency, report all fspaths in relative form. + fspath = bestrelpath(startpath, Path(fspath)) + keywords = getattr(event, "keywords", {}) + # Folding reports with global pytestmark variable. + # This is a workaround, because for now we cannot identify the scope of a skip marker + # TODO: Revisit after marks scope would be fixed. + if ( + event.when == "setup" + and "skip" in keywords + and "pytestmark" not in keywords + ): + key: tuple[str, int | None, str] = (fspath, None, reason) + else: + key = (fspath, lineno, reason) + d.setdefault(key, []).append(event) + values: list[tuple[int, str, int | None, str]] = [] + for key, events in d.items(): + values.append((len(events), *key)) + return values + + +_color_for_type = { + "failed": "red", + "error": "red", + "warnings": "yellow", + "passed": "green", +} +_color_for_type_default = "yellow" + + +def pluralize(count: int, noun: str) -> tuple[int, str]: + # No need to pluralize words such as `failed` or `passed`. + if noun not in ["error", "warnings", "test"]: + return count, noun + + # The `warnings` key is plural. To avoid API breakage, we keep it that way but + # set it to singular here so we can determine plurality in the same way as we do + # for `error`. + noun = noun.replace("warnings", "warning") + + return count, noun + "s" if count != 1 else noun + + +def _plugin_nameversions(plugininfo) -> list[str]: + values: list[str] = [] + for plugin, dist in plugininfo: + # Gets us name and version! + name = f"{dist.project_name}-{dist.version}" + # Questionable convenience, but it keeps things short. + if name.startswith("pytest-"): + name = name[7:] + # We decided to print python package names they can have more than one plugin. + if name not in values: + values.append(name) + return values + + +def format_session_duration(seconds: float) -> str: + """Format the given seconds in a human readable manner to show in the final summary.""" + if seconds < 60: + return f"{seconds:.2f}s" + else: + dt = datetime.timedelta(seconds=int(seconds)) + return f"{seconds:.2f}s ({dt})" + + +def _get_raw_skip_reason(report: TestReport) -> str: + """Get the reason string of a skip/xfail/xpass test report. + + The string is just the part given by the user. + """ + if hasattr(report, "wasxfail"): + reason = report.wasxfail + if reason.startswith("reason: "): + reason = reason[len("reason: ") :] + return reason + else: + assert report.skipped + assert isinstance(report.longrepr, tuple) + _, _, reason = report.longrepr + if reason.startswith("Skipped: "): + reason = reason[len("Skipped: ") :] + elif reason == "Skipped": + reason = "" + return reason diff --git a/.venv/Lib/site-packages/_pytest/threadexception.py b/.venv/Lib/site-packages/_pytest/threadexception.py new file mode 100644 index 0000000..c1ed803 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/threadexception.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +import threading +import traceback +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Generator +from typing import TYPE_CHECKING +import warnings + +import pytest + + +if TYPE_CHECKING: + from typing_extensions import Self + + +# Copied from cpython/Lib/test/support/threading_helper.py, with modifications. +class catch_threading_exception: + """Context manager catching threading.Thread exception using + threading.excepthook. + + Storing exc_value using a custom hook can create a reference cycle. The + reference cycle is broken explicitly when the context manager exits. + + Storing thread using a custom hook can resurrect it if it is set to an + object which is being finalized. Exiting the context manager clears the + stored object. + + Usage: + with threading_helper.catch_threading_exception() as cm: + # code spawning a thread which raises an exception + ... + # check the thread exception: use cm.args + ... + # cm.args attribute no longer exists at this point + # (to break a reference cycle) + """ + + def __init__(self) -> None: + self.args: threading.ExceptHookArgs | None = None + self._old_hook: Callable[[threading.ExceptHookArgs], Any] | None = None + + def _hook(self, args: threading.ExceptHookArgs) -> None: + self.args = args + + def __enter__(self) -> Self: + self._old_hook = threading.excepthook + threading.excepthook = self._hook + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + assert self._old_hook is not None + threading.excepthook = self._old_hook + self._old_hook = None + del self.args + + +def thread_exception_runtest_hook() -> Generator[None]: + with catch_threading_exception() as cm: + try: + yield + finally: + if cm.args: + thread_name = ( + "" if cm.args.thread is None else cm.args.thread.name + ) + msg = f"Exception in thread {thread_name}\n\n" + msg += "".join( + traceback.format_exception( + cm.args.exc_type, + cm.args.exc_value, + cm.args.exc_traceback, + ) + ) + warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) + + +@pytest.hookimpl(wrapper=True, trylast=True) +def pytest_runtest_setup() -> Generator[None]: + yield from thread_exception_runtest_hook() + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_call() -> Generator[None]: + yield from thread_exception_runtest_hook() + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_teardown() -> Generator[None]: + yield from thread_exception_runtest_hook() diff --git a/.venv/Lib/site-packages/_pytest/timing.py b/.venv/Lib/site-packages/_pytest/timing.py new file mode 100644 index 0000000..b23c7f6 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/timing.py @@ -0,0 +1,16 @@ +"""Indirection for time functions. + +We intentionally grab some "time" functions internally to avoid tests mocking "time" to affect +pytest runtime information (issue #185). + +Fixture "mock_timing" also interacts with this module for pytest's own tests. +""" + +from __future__ import annotations + +from time import perf_counter +from time import sleep +from time import time + + +__all__ = ["perf_counter", "sleep", "time"] diff --git a/.venv/Lib/site-packages/_pytest/tmpdir.py b/.venv/Lib/site-packages/_pytest/tmpdir.py new file mode 100644 index 0000000..1731a4b --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/tmpdir.py @@ -0,0 +1,314 @@ +# mypy: allow-untyped-defs +"""Support for providing temporary directories to test functions.""" + +from __future__ import annotations + +import dataclasses +import os +from pathlib import Path +import re +from shutil import rmtree +import tempfile +from typing import Any +from typing import Dict +from typing import final +from typing import Generator +from typing import Literal + +from .pathlib import cleanup_dead_symlinks +from .pathlib import LOCK_TIMEOUT +from .pathlib import make_numbered_dir +from .pathlib import make_numbered_dir_with_cleanup +from .pathlib import rm_rf +from _pytest.compat import get_user_id +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.deprecated import check_ispytest +from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureRequest +from _pytest.monkeypatch import MonkeyPatch +from _pytest.nodes import Item +from _pytest.reports import TestReport +from _pytest.stash import StashKey + + +tmppath_result_key = StashKey[Dict[str, bool]]() +RetentionType = Literal["all", "failed", "none"] + + +@final +@dataclasses.dataclass +class TempPathFactory: + """Factory for temporary directories under the common base temp directory, + as discussed at :ref:`temporary directory location and retention`. + """ + + _given_basetemp: Path | None + # pluggy TagTracerSub, not currently exposed, so Any. + _trace: Any + _basetemp: Path | None + _retention_count: int + _retention_policy: RetentionType + + def __init__( + self, + given_basetemp: Path | None, + retention_count: int, + retention_policy: RetentionType, + trace, + basetemp: Path | None = None, + *, + _ispytest: bool = False, + ) -> None: + check_ispytest(_ispytest) + if given_basetemp is None: + self._given_basetemp = None + else: + # Use os.path.abspath() to get absolute path instead of resolve() as it + # does not work the same in all platforms (see #4427). + # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012). + self._given_basetemp = Path(os.path.abspath(str(given_basetemp))) + self._trace = trace + self._retention_count = retention_count + self._retention_policy = retention_policy + self._basetemp = basetemp + + @classmethod + def from_config( + cls, + config: Config, + *, + _ispytest: bool = False, + ) -> TempPathFactory: + """Create a factory according to pytest configuration. + + :meta private: + """ + check_ispytest(_ispytest) + count = int(config.getini("tmp_path_retention_count")) + if count < 0: + raise ValueError( + f"tmp_path_retention_count must be >= 0. Current input: {count}." + ) + + policy = config.getini("tmp_path_retention_policy") + if policy not in ("all", "failed", "none"): + raise ValueError( + f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}." + ) + + return cls( + given_basetemp=config.option.basetemp, + trace=config.trace.get("tmpdir"), + retention_count=count, + retention_policy=policy, + _ispytest=True, + ) + + def _ensure_relative_to_basetemp(self, basename: str) -> str: + basename = os.path.normpath(basename) + if (self.getbasetemp() / basename).resolve().parent != self.getbasetemp(): + raise ValueError(f"{basename} is not a normalized and relative path") + return basename + + def mktemp(self, basename: str, numbered: bool = True) -> Path: + """Create a new temporary directory managed by the factory. + + :param basename: + Directory base name, must be a relative path. + + :param numbered: + If ``True``, ensure the directory is unique by adding a numbered + suffix greater than any existing one: ``basename="foo-"`` and ``numbered=True`` + means that this function will create directories named ``"foo-0"``, + ``"foo-1"``, ``"foo-2"`` and so on. + + :returns: + The path to the new directory. + """ + basename = self._ensure_relative_to_basetemp(basename) + if not numbered: + p = self.getbasetemp().joinpath(basename) + p.mkdir(mode=0o700) + else: + p = make_numbered_dir(root=self.getbasetemp(), prefix=basename, mode=0o700) + self._trace("mktemp", p) + return p + + def getbasetemp(self) -> Path: + """Return the base temporary directory, creating it if needed. + + :returns: + The base temporary directory. + """ + if self._basetemp is not None: + return self._basetemp + + if self._given_basetemp is not None: + basetemp = self._given_basetemp + if basetemp.exists(): + rm_rf(basetemp) + basetemp.mkdir(mode=0o700) + basetemp = basetemp.resolve() + else: + from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT") + temproot = Path(from_env or tempfile.gettempdir()).resolve() + user = get_user() or "unknown" + # use a sub-directory in the temproot to speed-up + # make_numbered_dir() call + rootdir = temproot.joinpath(f"pytest-of-{user}") + try: + rootdir.mkdir(mode=0o700, exist_ok=True) + except OSError: + # getuser() likely returned illegal characters for the platform, use unknown back off mechanism + rootdir = temproot.joinpath("pytest-of-unknown") + rootdir.mkdir(mode=0o700, exist_ok=True) + # Because we use exist_ok=True with a predictable name, make sure + # we are the owners, to prevent any funny business (on unix, where + # temproot is usually shared). + # Also, to keep things private, fixup any world-readable temp + # rootdir's permissions. Historically 0o755 was used, so we can't + # just error out on this, at least for a while. + uid = get_user_id() + if uid is not None: + rootdir_stat = rootdir.stat() + if rootdir_stat.st_uid != uid: + raise OSError( + f"The temporary directory {rootdir} is not owned by the current user. " + "Fix this and try again." + ) + if (rootdir_stat.st_mode & 0o077) != 0: + os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) + keep = self._retention_count + if self._retention_policy == "none": + keep = 0 + basetemp = make_numbered_dir_with_cleanup( + prefix="pytest-", + root=rootdir, + keep=keep, + lock_timeout=LOCK_TIMEOUT, + mode=0o700, + ) + assert basetemp is not None, basetemp + self._basetemp = basetemp + self._trace("new basetemp", basetemp) + return basetemp + + +def get_user() -> str | None: + """Return the current user name, or None if getuser() does not work + in the current environment (see #1010).""" + try: + # In some exotic environments, getpass may not be importable. + import getpass + + return getpass.getuser() + except (ImportError, OSError, KeyError): + return None + + +def pytest_configure(config: Config) -> None: + """Create a TempPathFactory and attach it to the config object. + + This is to comply with existing plugins which expect the handler to be + available at pytest_configure time, but ideally should be moved entirely + to the tmp_path_factory session fixture. + """ + mp = MonkeyPatch() + config.add_cleanup(mp.undo) + _tmp_path_factory = TempPathFactory.from_config(config, _ispytest=True) + mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False) + + +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "tmp_path_retention_count", + help="How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`.", + default=3, + ) + + parser.addini( + "tmp_path_retention_policy", + help="Controls which directories created by the `tmp_path` fixture are kept around, based on test outcome. " + "(all/failed/none)", + default="all", + ) + + +@fixture(scope="session") +def tmp_path_factory(request: FixtureRequest) -> TempPathFactory: + """Return a :class:`pytest.TempPathFactory` instance for the test session.""" + # Set dynamically by pytest_configure() above. + return request.config._tmp_path_factory # type: ignore + + +def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: + name = request.node.name + name = re.sub(r"[\W]", "_", name) + MAXVAL = 30 + name = name[:MAXVAL] + return factory.mktemp(name, numbered=True) + + +@fixture +def tmp_path( + request: FixtureRequest, tmp_path_factory: TempPathFactory +) -> Generator[Path]: + """Return a temporary directory (as :class:`pathlib.Path` object) + which is unique to each test function invocation. + The temporary directory is created as a subdirectory + of the base temporary directory, with configurable retention, + as discussed in :ref:`temporary directory location and retention`. + """ + path = _mk_tmp(request, tmp_path_factory) + yield path + + # Remove the tmpdir if the policy is "failed" and the test passed. + tmp_path_factory: TempPathFactory = request.session.config._tmp_path_factory # type: ignore + policy = tmp_path_factory._retention_policy + result_dict = request.node.stash[tmppath_result_key] + + if policy == "failed" and result_dict.get("call", True): + # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, + # permissions, etc, in which case we ignore it. + rmtree(path, ignore_errors=True) + + del request.node.stash[tmppath_result_key] + + +def pytest_sessionfinish(session, exitstatus: int | ExitCode): + """After each session, remove base directory if all the tests passed, + the policy is "failed", and the basetemp is not specified by a user. + """ + tmp_path_factory: TempPathFactory = session.config._tmp_path_factory + basetemp = tmp_path_factory._basetemp + if basetemp is None: + return + + policy = tmp_path_factory._retention_policy + if ( + exitstatus == 0 + and policy == "failed" + and tmp_path_factory._given_basetemp is None + ): + if basetemp.is_dir(): + # We do a "best effort" to remove files, but it might not be possible due to some leaked resource, + # permissions, etc, in which case we ignore it. + rmtree(basetemp, ignore_errors=True) + + # Remove dead symlinks. + if basetemp.is_dir(): + cleanup_dead_symlinks(basetemp) + + +@hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_makereport( + item: Item, call +) -> Generator[None, TestReport, TestReport]: + rep = yield + assert rep.when is not None + empty: dict[str, bool] = {} + item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed + return rep diff --git a/.venv/Lib/site-packages/_pytest/unittest.py b/.venv/Lib/site-packages/_pytest/unittest.py new file mode 100644 index 0000000..8cecd4f --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/unittest.py @@ -0,0 +1,435 @@ +# mypy: allow-untyped-defs +"""Discover and run std-library "unittest" style tests.""" + +from __future__ import annotations + +import inspect +import sys +import traceback +import types +from typing import Any +from typing import Callable +from typing import Generator +from typing import Iterable +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union + +import _pytest._code +from _pytest.compat import is_async_function +from _pytest.config import hookimpl +from _pytest.fixtures import FixtureRequest +from _pytest.nodes import Collector +from _pytest.nodes import Item +from _pytest.outcomes import exit +from _pytest.outcomes import fail +from _pytest.outcomes import skip +from _pytest.outcomes import xfail +from _pytest.python import Class +from _pytest.python import Function +from _pytest.python import Module +from _pytest.runner import CallInfo +import pytest + + +if sys.version_info[:2] < (3, 11): + from exceptiongroup import ExceptionGroup + +if TYPE_CHECKING: + import unittest + + import twisted.trial.unittest + + +_SysExcInfoType = Union[ + Tuple[Type[BaseException], BaseException, types.TracebackType], + Tuple[None, None, None], +] + + +def pytest_pycollect_makeitem( + collector: Module | Class, name: str, obj: object +) -> UnitTestCase | None: + try: + # Has unittest been imported? + ut = sys.modules["unittest"] + # Is obj a subclass of unittest.TestCase? + # Type ignored because `ut` is an opaque module. + if not issubclass(obj, ut.TestCase): # type: ignore + return None + except Exception: + return None + # Is obj a concrete class? + # Abstract classes can't be instantiated so no point collecting them. + if inspect.isabstract(obj): + return None + # Yes, so let's collect it. + return UnitTestCase.from_parent(collector, name=name, obj=obj) + + +class UnitTestCase(Class): + # Marker for fixturemanger.getfixtureinfo() + # to declare that our children do not support funcargs. + nofuncargs = True + + def newinstance(self): + # TestCase __init__ takes the method (test) name. The TestCase + # constructor treats the name "runTest" as a special no-op, so it can be + # used when a dummy instance is needed. While unittest.TestCase has a + # default, some subclasses omit the default (#9610), so always supply + # it. + return self.obj("runTest") + + def collect(self) -> Iterable[Item | Collector]: + from unittest import TestLoader + + cls = self.obj + if not getattr(cls, "__test__", True): + return + + skipped = _is_skipped(cls) + if not skipped: + self._register_unittest_setup_method_fixture(cls) + self._register_unittest_setup_class_fixture(cls) + self._register_setup_class_fixture() + + self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) + + loader = TestLoader() + foundsomething = False + for name in loader.getTestCaseNames(self.obj): + x = getattr(self.obj, name) + if not getattr(x, "__test__", True): + continue + yield TestCaseFunction.from_parent(self, name=name) + foundsomething = True + + if not foundsomething: + runtest = getattr(self.obj, "runTest", None) + if runtest is not None: + ut = sys.modules.get("twisted.trial.unittest", None) + if ut is None or runtest != ut.TestCase.runTest: + yield TestCaseFunction.from_parent(self, name="runTest") + + def _register_unittest_setup_class_fixture(self, cls: type) -> None: + """Register an auto-use fixture to invoke setUpClass and + tearDownClass (#517).""" + setup = getattr(cls, "setUpClass", None) + teardown = getattr(cls, "tearDownClass", None) + if setup is None and teardown is None: + return None + cleanup = getattr(cls, "doClassCleanups", lambda: None) + + def process_teardown_exceptions() -> None: + # tearDown_exceptions is a list set in the class containing exc_infos for errors during + # teardown for the class. + exc_infos = getattr(cls, "tearDown_exceptions", None) + if not exc_infos: + return + exceptions = [exc for (_, exc, _) in exc_infos] + # If a single exception, raise it directly as this provides a more readable + # error (hopefully this will improve in #12255). + if len(exceptions) == 1: + raise exceptions[0] + else: + raise ExceptionGroup("Unittest class cleanup errors", exceptions) + + def unittest_setup_class_fixture( + request: FixtureRequest, + ) -> Generator[None]: + cls = request.cls + if _is_skipped(cls): + reason = cls.__unittest_skip_why__ + raise pytest.skip.Exception(reason, _use_item_location=True) + if setup is not None: + try: + setup() + # unittest does not call the cleanup function for every BaseException, so we + # follow this here. + except Exception: + cleanup() + process_teardown_exceptions() + raise + yield + try: + if teardown is not None: + teardown() + finally: + cleanup() + process_teardown_exceptions() + + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_unittest_setUpClass_fixture_{cls.__qualname__}", + func=unittest_setup_class_fixture, + nodeid=self.nodeid, + scope="class", + autouse=True, + ) + + def _register_unittest_setup_method_fixture(self, cls: type) -> None: + """Register an auto-use fixture to invoke setup_method and + teardown_method (#517).""" + setup = getattr(cls, "setup_method", None) + teardown = getattr(cls, "teardown_method", None) + if setup is None and teardown is None: + return None + + def unittest_setup_method_fixture( + request: FixtureRequest, + ) -> Generator[None]: + self = request.instance + if _is_skipped(self): + reason = self.__unittest_skip_why__ + raise pytest.skip.Exception(reason, _use_item_location=True) + if setup is not None: + setup(self, request.function) + yield + if teardown is not None: + teardown(self, request.function) + + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_unittest_setup_method_fixture_{cls.__qualname__}", + func=unittest_setup_method_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) + + +class TestCaseFunction(Function): + nofuncargs = True + _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None + + def _getinstance(self): + assert isinstance(self.parent, UnitTestCase) + return self.parent.obj(self.name) + + # Backward compat for pytest-django; can be removed after pytest-django + # updates + some slack. + @property + def _testcase(self): + return self.instance + + def setup(self) -> None: + # A bound method to be called during teardown() if set (see 'runtest()'). + self._explicit_tearDown: Callable[[], None] | None = None + super().setup() + + def teardown(self) -> None: + if self._explicit_tearDown is not None: + self._explicit_tearDown() + self._explicit_tearDown = None + self._obj = None + del self._instance + super().teardown() + + def startTest(self, testcase: unittest.TestCase) -> None: + pass + + def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: + # Unwrap potential exception info (see twisted trial support below). + rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) + try: + excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info( + rawexcinfo # type: ignore[arg-type] + ) + # Invoke the attributes to trigger storing the traceback + # trial causes some issue there. + _ = excinfo.value + _ = excinfo.traceback + except TypeError: + try: + try: + values = traceback.format_exception(*rawexcinfo) + values.insert( + 0, + "NOTE: Incompatible Exception Representation, " + "displaying natively:\n\n", + ) + fail("".join(values), pytrace=False) + except (fail.Exception, KeyboardInterrupt): + raise + except BaseException: + fail( + "ERROR: Unknown Incompatible Exception " + f"representation:\n{rawexcinfo!r}", + pytrace=False, + ) + except KeyboardInterrupt: + raise + except fail.Exception: + excinfo = _pytest._code.ExceptionInfo.from_current() + self.__dict__.setdefault("_excinfo", []).append(excinfo) + + def addError( + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType + ) -> None: + try: + if isinstance(rawexcinfo[1], exit.Exception): + exit(rawexcinfo[1].msg) + except TypeError: + pass + self._addexcinfo(rawexcinfo) + + def addFailure( + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType + ) -> None: + self._addexcinfo(rawexcinfo) + + def addSkip(self, testcase: unittest.TestCase, reason: str) -> None: + try: + raise pytest.skip.Exception(reason, _use_item_location=True) + except skip.Exception: + self._addexcinfo(sys.exc_info()) + + def addExpectedFailure( + self, + testcase: unittest.TestCase, + rawexcinfo: _SysExcInfoType, + reason: str = "", + ) -> None: + try: + xfail(str(reason)) + except xfail.Exception: + self._addexcinfo(sys.exc_info()) + + def addUnexpectedSuccess( + self, + testcase: unittest.TestCase, + reason: twisted.trial.unittest.Todo | None = None, + ) -> None: + msg = "Unexpected success" + if reason: + msg += f": {reason.reason}" + # Preserve unittest behaviour - fail the test. Explicitly not an XPASS. + try: + fail(msg, pytrace=False) + except fail.Exception: + self._addexcinfo(sys.exc_info()) + + def addSuccess(self, testcase: unittest.TestCase) -> None: + pass + + def stopTest(self, testcase: unittest.TestCase) -> None: + pass + + def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None: + pass + + def runtest(self) -> None: + from _pytest.debugging import maybe_wrap_pytest_function_for_tracing + + testcase = self.instance + assert testcase is not None + + maybe_wrap_pytest_function_for_tracing(self) + + # Let the unittest framework handle async functions. + if is_async_function(self.obj): + testcase(result=self) + else: + # When --pdb is given, we want to postpone calling tearDown() otherwise + # when entering the pdb prompt, tearDown() would have probably cleaned up + # instance variables, which makes it difficult to debug. + # Arguably we could always postpone tearDown(), but this changes the moment where the + # TestCase instance interacts with the results object, so better to only do it + # when absolutely needed. + # We need to consider if the test itself is skipped, or the whole class. + assert isinstance(self.parent, UnitTestCase) + skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj) + if self.config.getoption("usepdb") and not skipped: + self._explicit_tearDown = testcase.tearDown + setattr(testcase, "tearDown", lambda *args: None) + + # We need to update the actual bound method with self.obj, because + # wrap_pytest_function_for_tracing replaces self.obj by a wrapper. + setattr(testcase, self.name, self.obj) + try: + testcase(result=self) + finally: + delattr(testcase, self.name) + + def _traceback_filter( + self, excinfo: _pytest._code.ExceptionInfo[BaseException] + ) -> _pytest._code.Traceback: + traceback = super()._traceback_filter(excinfo) + ntraceback = traceback.filter( + lambda x: not x.frame.f_globals.get("__unittest"), + ) + if not ntraceback: + ntraceback = traceback + return ntraceback + + +@hookimpl(tryfirst=True) +def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: + if isinstance(item, TestCaseFunction): + if item._excinfo: + call.excinfo = item._excinfo.pop(0) + try: + del call.result + except AttributeError: + pass + + # Convert unittest.SkipTest to pytest.skip. + # This is actually only needed for nose, which reuses unittest.SkipTest for + # its own nose.SkipTest. For unittest TestCases, SkipTest is already + # handled internally, and doesn't reach here. + unittest = sys.modules.get("unittest") + if unittest and call.excinfo and isinstance(call.excinfo.value, unittest.SkipTest): + excinfo = call.excinfo + call2 = CallInfo[None].from_call( + lambda: pytest.skip(str(excinfo.value)), call.when + ) + call.excinfo = call2.excinfo + + +# Twisted trial support. +classImplements_has_run = False + + +@hookimpl(wrapper=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: + if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules: + ut: Any = sys.modules["twisted.python.failure"] + global classImplements_has_run + Failure__init__ = ut.Failure.__init__ + if not classImplements_has_run: + from twisted.trial.itrial import IReporter + from zope.interface import classImplements + + classImplements(TestCaseFunction, IReporter) + classImplements_has_run = True + + def excstore( + self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None + ): + if exc_value is None: + self._rawexcinfo = sys.exc_info() + else: + if exc_type is None: + exc_type = type(exc_value) + self._rawexcinfo = (exc_type, exc_value, exc_tb) + try: + Failure__init__( + self, exc_value, exc_type, exc_tb, captureVars=captureVars + ) + except TypeError: + Failure__init__(self, exc_value, exc_type, exc_tb) + + ut.Failure.__init__ = excstore + try: + res = yield + finally: + ut.Failure.__init__ = Failure__init__ + else: + res = yield + return res + + +def _is_skipped(obj) -> bool: + """Return True if the given object has been marked with @unittest.skip.""" + return bool(getattr(obj, "__unittest_skip__", False)) diff --git a/.venv/Lib/site-packages/_pytest/unraisableexception.py b/.venv/Lib/site-packages/_pytest/unraisableexception.py new file mode 100644 index 0000000..77a2de2 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/unraisableexception.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +import sys +import traceback +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Generator +from typing import TYPE_CHECKING +import warnings + +import pytest + + +if TYPE_CHECKING: + from typing_extensions import Self + + +# Copied from cpython/Lib/test/support/__init__.py, with modifications. +class catch_unraisable_exception: + """Context manager catching unraisable exception using sys.unraisablehook. + + Storing the exception value (cm.unraisable.exc_value) creates a reference + cycle. The reference cycle is broken explicitly when the context manager + exits. + + Storing the object (cm.unraisable.object) can resurrect it if it is set to + an object which is being finalized. Exiting the context manager clears the + stored object. + + Usage: + with catch_unraisable_exception() as cm: + # code creating an "unraisable exception" + ... + # check the unraisable exception: use cm.unraisable + ... + # cm.unraisable attribute no longer exists at this point + # (to break a reference cycle) + """ + + def __init__(self) -> None: + self.unraisable: sys.UnraisableHookArgs | None = None + self._old_hook: Callable[[sys.UnraisableHookArgs], Any] | None = None + + def _hook(self, unraisable: sys.UnraisableHookArgs) -> None: + # Storing unraisable.object can resurrect an object which is being + # finalized. Storing unraisable.exc_value creates a reference cycle. + self.unraisable = unraisable + + def __enter__(self) -> Self: + self._old_hook = sys.unraisablehook + sys.unraisablehook = self._hook + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + assert self._old_hook is not None + sys.unraisablehook = self._old_hook + self._old_hook = None + del self.unraisable + + +def unraisable_exception_runtest_hook() -> Generator[None]: + with catch_unraisable_exception() as cm: + try: + yield + finally: + if cm.unraisable: + if cm.unraisable.err_msg is not None: + err_msg = cm.unraisable.err_msg + else: + err_msg = "Exception ignored in" + msg = f"{err_msg}: {cm.unraisable.object!r}\n\n" + msg += "".join( + traceback.format_exception( + cm.unraisable.exc_type, + cm.unraisable.exc_value, + cm.unraisable.exc_traceback, + ) + ) + warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_setup() -> Generator[None]: + yield from unraisable_exception_runtest_hook() + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_call() -> Generator[None]: + yield from unraisable_exception_runtest_hook() + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_teardown() -> Generator[None]: + yield from unraisable_exception_runtest_hook() diff --git a/.venv/Lib/site-packages/_pytest/warning_types.py b/.venv/Lib/site-packages/_pytest/warning_types.py new file mode 100644 index 0000000..4ab14e4 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/warning_types.py @@ -0,0 +1,166 @@ +from __future__ import annotations + +import dataclasses +import inspect +from types import FunctionType +from typing import Any +from typing import final +from typing import Generic +from typing import TypeVar +import warnings + + +class PytestWarning(UserWarning): + """Base class for all warnings emitted by pytest.""" + + __module__ = "pytest" + + +@final +class PytestAssertRewriteWarning(PytestWarning): + """Warning emitted by the pytest assert rewrite module.""" + + __module__ = "pytest" + + +@final +class PytestCacheWarning(PytestWarning): + """Warning emitted by the cache plugin in various situations.""" + + __module__ = "pytest" + + +@final +class PytestConfigWarning(PytestWarning): + """Warning emitted for configuration issues.""" + + __module__ = "pytest" + + +@final +class PytestCollectionWarning(PytestWarning): + """Warning emitted when pytest is not able to collect a file or symbol in a module.""" + + __module__ = "pytest" + + +class PytestDeprecationWarning(PytestWarning, DeprecationWarning): + """Warning class for features that will be removed in a future version.""" + + __module__ = "pytest" + + +class PytestRemovedIn9Warning(PytestDeprecationWarning): + """Warning class for features that will be removed in pytest 9.""" + + __module__ = "pytest" + + +class PytestReturnNotNoneWarning(PytestWarning): + """Warning emitted when a test function is returning value other than None.""" + + __module__ = "pytest" + + +@final +class PytestExperimentalApiWarning(PytestWarning, FutureWarning): + """Warning category used to denote experiments in pytest. + + Use sparingly as the API might change or even be removed completely in a + future version. + """ + + __module__ = "pytest" + + @classmethod + def simple(cls, apiname: str) -> PytestExperimentalApiWarning: + return cls(f"{apiname} is an experimental api that may change over time") + + +@final +class PytestUnhandledCoroutineWarning(PytestReturnNotNoneWarning): + """Warning emitted for an unhandled coroutine. + + A coroutine was encountered when collecting test functions, but was not + handled by any async-aware plugin. + Coroutine test functions are not natively supported. + """ + + __module__ = "pytest" + + +@final +class PytestUnknownMarkWarning(PytestWarning): + """Warning emitted on use of unknown markers. + + See :ref:`mark` for details. + """ + + __module__ = "pytest" + + +@final +class PytestUnraisableExceptionWarning(PytestWarning): + """An unraisable exception was reported. + + Unraisable exceptions are exceptions raised in :meth:`__del__ ` + implementations and similar situations when the exception cannot be raised + as normal. + """ + + __module__ = "pytest" + + +@final +class PytestUnhandledThreadExceptionWarning(PytestWarning): + """An unhandled exception occurred in a :class:`~threading.Thread`. + + Such exceptions don't propagate normally. + """ + + __module__ = "pytest" + + +_W = TypeVar("_W", bound=PytestWarning) + + +@final +@dataclasses.dataclass +class UnformattedWarning(Generic[_W]): + """A warning meant to be formatted during runtime. + + This is used to hold warnings that need to format their message at runtime, + as opposed to a direct message. + """ + + category: type[_W] + template: str + + def format(self, **kwargs: Any) -> _W: + """Return an instance of the warning category, formatted with given kwargs.""" + return self.category(self.template.format(**kwargs)) + + +def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None: + """ + Issue the warning :param:`message` for the definition of the given :param:`method` + + this helps to log warnings for functions defined prior to finding an issue with them + (like hook wrappers being marked in a legacy mechanism) + """ + lineno = method.__code__.co_firstlineno + filename = inspect.getfile(method) + module = method.__module__ + mod_globals = method.__globals__ + try: + warnings.warn_explicit( + message, + type(message), + filename=filename, + module=module, + registry=mod_globals.setdefault("__warningregistry__", {}), + lineno=lineno, + ) + except Warning as w: + # If warnings are errors (e.g. -Werror), location information gets lost, so we add it to the message. + raise type(w)(f"{w}\n at {filename}:{lineno}") from None diff --git a/.venv/Lib/site-packages/_pytest/warnings.py b/.venv/Lib/site-packages/_pytest/warnings.py new file mode 100644 index 0000000..eeb4772 --- /dev/null +++ b/.venv/Lib/site-packages/_pytest/warnings.py @@ -0,0 +1,151 @@ +# mypy: allow-untyped-defs +from __future__ import annotations + +from contextlib import contextmanager +import sys +from typing import Generator +from typing import Literal +import warnings + +from _pytest.config import apply_warning_filters +from _pytest.config import Config +from _pytest.config import parse_warning_filter +from _pytest.main import Session +from _pytest.nodes import Item +from _pytest.terminal import TerminalReporter +import pytest + + +def pytest_configure(config: Config) -> None: + config.addinivalue_line( + "markers", + "filterwarnings(warning): add a warning filter to the given test. " + "see https://docs.pytest.org/en/stable/how-to/capture-warnings.html#pytest-mark-filterwarnings ", + ) + + +@contextmanager +def catch_warnings_for_item( + config: Config, + ihook, + when: Literal["config", "collect", "runtest"], + item: Item | None, +) -> Generator[None]: + """Context manager that catches warnings generated in the contained execution block. + + ``item`` can be None if we are not in the context of an item execution. + + Each warning captured triggers the ``pytest_warning_recorded`` hook. + """ + config_filters = config.getini("filterwarnings") + cmdline_filters = config.known_args_namespace.pythonwarnings or [] + with warnings.catch_warnings(record=True) as log: + # mypy can't infer that record=True means log is not None; help it. + assert log is not None + + if not sys.warnoptions: + # If user is not explicitly configuring warning filters, show deprecation warnings by default (#2908). + warnings.filterwarnings("always", category=DeprecationWarning) + warnings.filterwarnings("always", category=PendingDeprecationWarning) + + # To be enabled in pytest 9.0.0. + # warnings.filterwarnings("error", category=pytest.PytestRemovedIn9Warning) + + apply_warning_filters(config_filters, cmdline_filters) + + # apply filters from "filterwarnings" marks + nodeid = "" if item is None else item.nodeid + if item is not None: + for mark in item.iter_markers(name="filterwarnings"): + for arg in mark.args: + warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) + + try: + yield + finally: + for warning_message in log: + ihook.pytest_warning_recorded.call_historic( + kwargs=dict( + warning_message=warning_message, + nodeid=nodeid, + when=when, + location=None, + ) + ) + + +def warning_record_to_str(warning_message: warnings.WarningMessage) -> str: + """Convert a warnings.WarningMessage to a string.""" + warn_msg = warning_message.message + msg = warnings.formatwarning( + str(warn_msg), + warning_message.category, + warning_message.filename, + warning_message.lineno, + warning_message.line, + ) + if warning_message.source is not None: + try: + import tracemalloc + except ImportError: + pass + else: + tb = tracemalloc.get_object_traceback(warning_message.source) + if tb is not None: + formatted_tb = "\n".join(tb.format()) + # Use a leading new line to better separate the (large) output + # from the traceback to the previous warning text. + msg += f"\nObject allocated at:\n{formatted_tb}" + else: + # No need for a leading new line. + url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings" + msg += "Enable tracemalloc to get traceback where the object was allocated.\n" + msg += f"See {url} for more info." + return msg + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: + with catch_warnings_for_item( + config=item.config, ihook=item.ihook, when="runtest", item=item + ): + return (yield) + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_collection(session: Session) -> Generator[None, object, object]: + config = session.config + with catch_warnings_for_item( + config=config, ihook=config.hook, when="collect", item=None + ): + return (yield) + + +@pytest.hookimpl(wrapper=True) +def pytest_terminal_summary( + terminalreporter: TerminalReporter, +) -> Generator[None]: + config = terminalreporter.config + with catch_warnings_for_item( + config=config, ihook=config.hook, when="config", item=None + ): + return (yield) + + +@pytest.hookimpl(wrapper=True) +def pytest_sessionfinish(session: Session) -> Generator[None]: + config = session.config + with catch_warnings_for_item( + config=config, ihook=config.hook, when="config", item=None + ): + return (yield) + + +@pytest.hookimpl(wrapper=True) +def pytest_load_initial_conftests( + early_config: Config, +) -> Generator[None]: + with catch_warnings_for_item( + config=early_config, ihook=early_config.hook, when="config", item=None + ): + return (yield) diff --git a/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA new file mode 100644 index 0000000..3ac05cf --- /dev/null +++ b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA @@ -0,0 +1,295 @@ +Metadata-Version: 2.3 +Name: annotated-types +Version: 0.7.0 +Summary: Reusable constraint types to use with typing.Annotated +Project-URL: Homepage, https://github.com/annotated-types/annotated-types +Project-URL: Source, https://github.com/annotated-types/annotated-types +Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases +Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin , Zac Hatfield-Dodds +License-File: LICENSE +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Console +Classifier: Environment :: MacOS X +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Unix +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Typing :: Typed +Requires-Python: >=3.8 +Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9' +Description-Content-Type: text/markdown + +# annotated-types + +[![CI](https://github.com/annotated-types/annotated-types/workflows/CI/badge.svg?event=push)](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) +[![pypi](https://img.shields.io/pypi/v/annotated-types.svg)](https://pypi.python.org/pypi/annotated-types) +[![versions](https://img.shields.io/pypi/pyversions/annotated-types.svg)](https://github.com/annotated-types/annotated-types) +[![license](https://img.shields.io/github/license/annotated-types/annotated-types.svg)](https://github.com/annotated-types/annotated-types/blob/main/LICENSE) + +[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of +adding context-specific metadata to existing types, and specifies that +`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special +logic for `x`. + +This package provides metadata objects which can be used to represent common +constraints such as upper and lower bounds on scalar values and collection sizes, +a `Predicate` marker for runtime checks, and +descriptions of how we intend these metadata to be interpreted. In some cases, +we also note alternative representations which do not require this package. + +## Install + +```bash +pip install annotated-types +``` + +## Examples + +```python +from typing import Annotated +from annotated_types import Gt, Len, Predicate + +class MyClass: + age: Annotated[int, Gt(18)] # Valid: 19, 20, ... + # Invalid: 17, 18, "19", 19.0, ... + factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ... + # Invalid: 4, 8, -2, 5.0, "prime", ... + + my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50] + # Invalid: (1, 2), ["abc"], [0] * 20 +``` + +## Documentation + +_While `annotated-types` avoids runtime checks for performance, users should not +construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`. +Downstream implementors may choose to raise an error, emit a warning, silently ignore +a metadata item, etc., if the metadata objects described below are used with an +incompatible type - or for any other reason!_ + +### Gt, Ge, Lt, Le + +Express inclusive and/or exclusive bounds on orderable values - which may be numbers, +dates, times, strings, sets, etc. Note that the boundary value need not be of the +same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]` +is fine, for example, and implies that the value is an integer x such that `x > 1.5`. + +We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)` +as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on +the `annotated-types` package. + +To be explicit, these types have the following meanings: + +* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum +* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum +* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum +* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum + +### Interval + +`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single +metadata object. `None` attributes should be ignored, and non-`None` attributes +treated as per the single bounds above. + +### MultipleOf + +`MultipleOf(multiple_of=x)` might be interpreted in two ways: + +1. Python semantics, implying `value % multiple_of == 0`, or +2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1), + where `int(value / multiple_of) == value / multiple_of`. + +We encourage users to be aware of these two common interpretations and their +distinct behaviours, especially since very large or non-integer numbers make +it easy to cause silent data corruption due to floating-point imprecision. + +We encourage libraries to carefully document which interpretation they implement. + +### MinLen, MaxLen, Len + +`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive. + +As well as `Len()` which can optionally include upper and lower bounds, we also +provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)` +and `Len(max_length=y)` respectively. + +`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`. + +Examples of usage: + +* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less +* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less +* `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more +* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6 +* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8 + +#### Changed in v0.4.0 + +* `min_inclusive` has been renamed to `min_length`, no change in meaning +* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive** +* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic + meaning of the upper bound in slices vs. `Len` + +See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion. + +### Timezone + +`Timezone` can be used with a `datetime` or a `time` to express which timezones +are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime. +`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis)) +expresses that any timezone-aware datetime is allowed. You may also pass a specific +timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) +object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only +allow a specific timezone, though we note that this is often a symptom of fragile design. + +#### Changed in v0.x.x + +* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of + `timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries. + +### Unit + +`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of +a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]` +would be a float representing a velocity in meters per second. + +Please note that `annotated_types` itself makes no attempt to parse or validate +the unit string in any way. That is left entirely to downstream libraries, +such as [`pint`](https://pint.readthedocs.io) or +[`astropy.units`](https://docs.astropy.org/en/stable/units/). + +An example of how a library might use this metadata: + +```python +from annotated_types import Unit +from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args + +# given a type annotated with a unit: +Meters = Annotated[float, Unit("m")] + + +# you can cast the annotation to a specific unit type with any +# callable that accepts a string and returns the desired type +T = TypeVar("T") +def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None: + if get_origin(tp) is Annotated: + for arg in get_args(tp): + if isinstance(arg, Unit): + return unit_cls(arg.unit) + return None + + +# using `pint` +import pint +pint_unit = cast_unit(Meters, pint.Unit) + + +# using `astropy.units` +import astropy.units as u +astropy_unit = cast_unit(Meters, u.Unit) +``` + +### Predicate + +`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values. +Users should prefer the statically inspectable metadata above, but if you need +the full power and flexibility of arbitrary runtime predicates... here it is. + +For some common constraints, we provide generic types: + +* `IsLower = Annotated[T, Predicate(str.islower)]` +* `IsUpper = Annotated[T, Predicate(str.isupper)]` +* `IsDigit = Annotated[T, Predicate(str.isdigit)]` +* `IsFinite = Annotated[T, Predicate(math.isfinite)]` +* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]` +* `IsNan = Annotated[T, Predicate(math.isnan)]` +* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]` +* `IsInfinite = Annotated[T, Predicate(math.isinf)]` +* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]` + +so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer +(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`. + +Some libraries might have special logic to handle known or understandable predicates, +for example by checking for `str.isdigit` and using its presence to both call custom +logic to enforce digit-only strings, and customise some generated external schema. +Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in +favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`. + +To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner. + +We do not specify what behaviour should be expected for predicates that raise +an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently +skip invalid constraints, or statically raise an error; or it might try calling it +and then propagate or discard the resulting +`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object` +exception. We encourage libraries to document the behaviour they choose. + +### Doc + +`doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used. + +It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools. + +It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`. + +This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md). + +### Integrating downstream types with `GroupedMetadata` + +Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata. +This can help reduce verbosity and cognitive overhead for users. +For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata: + +```python +from dataclasses import dataclass +from typing import Iterator +from annotated_types import GroupedMetadata, Ge + +@dataclass +class Field(GroupedMetadata): + ge: int | None = None + description: str | None = None + + def __iter__(self) -> Iterator[object]: + # Iterating over a GroupedMetadata object should yield annotated-types + # constraint metadata objects which describe it as fully as possible, + # and may include other unknown objects too. + if self.ge is not None: + yield Ge(self.ge) + if self.description is not None: + yield Description(self.description) +``` + +Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently. + +Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself. + +Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`. + +### Consuming metadata + +We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103). + +It is up to the implementer to determine how this metadata is used. +You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases. + +## Design & History + +This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic +and Hypothesis, with the goal of making it as easy as possible for end-users to +provide more informative annotations for use by runtime libraries. + +It is deliberately minimal, and following PEP-593 allows considerable downstream +discretion in what (if anything!) they choose to support. Nonetheless, we expect +that staying simple and covering _only_ the most common use-cases will give users +and maintainers the best experience we can. If you'd like more constraints for your +types - follow our lead, by defining them and documenting them downstream! diff --git a/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD new file mode 100644 index 0000000..7045729 --- /dev/null +++ b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD @@ -0,0 +1,10 @@ +annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046 +annotated_types-0.7.0.dist-info/RECORD,, +annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87 +annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083 +annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819 +annotated_types/__pycache__/__init__.cpython-312.pyc,, +annotated_types/__pycache__/test_cases.cpython-312.pyc,, +annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421 diff --git a/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL new file mode 100644 index 0000000..516596c --- /dev/null +++ b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.24.2 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..d99323a --- /dev/null +++ b/.venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 the contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.venv/Lib/site-packages/annotated_types/__init__.py b/.venv/Lib/site-packages/annotated_types/__init__.py new file mode 100644 index 0000000..74e0dee --- /dev/null +++ b/.venv/Lib/site-packages/annotated_types/__init__.py @@ -0,0 +1,432 @@ +import math +import sys +import types +from dataclasses import dataclass +from datetime import tzinfo +from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union + +if sys.version_info < (3, 8): + from typing_extensions import Protocol, runtime_checkable +else: + from typing import Protocol, runtime_checkable + +if sys.version_info < (3, 9): + from typing_extensions import Annotated, Literal +else: + from typing import Annotated, Literal + +if sys.version_info < (3, 10): + EllipsisType = type(Ellipsis) + KW_ONLY = {} + SLOTS = {} +else: + from types import EllipsisType + + KW_ONLY = {"kw_only": True} + SLOTS = {"slots": True} + + +__all__ = ( + 'BaseMetadata', + 'GroupedMetadata', + 'Gt', + 'Ge', + 'Lt', + 'Le', + 'Interval', + 'MultipleOf', + 'MinLen', + 'MaxLen', + 'Len', + 'Timezone', + 'Predicate', + 'LowerCase', + 'UpperCase', + 'IsDigits', + 'IsFinite', + 'IsNotFinite', + 'IsNan', + 'IsNotNan', + 'IsInfinite', + 'IsNotInfinite', + 'doc', + 'DocInfo', + '__version__', +) + +__version__ = '0.7.0' + + +T = TypeVar('T') + + +# arguments that start with __ are considered +# positional only +# see https://peps.python.org/pep-0484/#positional-only-arguments + + +class SupportsGt(Protocol): + def __gt__(self: T, __other: T) -> bool: + ... + + +class SupportsGe(Protocol): + def __ge__(self: T, __other: T) -> bool: + ... + + +class SupportsLt(Protocol): + def __lt__(self: T, __other: T) -> bool: + ... + + +class SupportsLe(Protocol): + def __le__(self: T, __other: T) -> bool: + ... + + +class SupportsMod(Protocol): + def __mod__(self: T, __other: T) -> T: + ... + + +class SupportsDiv(Protocol): + def __div__(self: T, __other: T) -> T: + ... + + +class BaseMetadata: + """Base class for all metadata. + + This exists mainly so that implementers + can do `isinstance(..., BaseMetadata)` while traversing field annotations. + """ + + __slots__ = () + + +@dataclass(frozen=True, **SLOTS) +class Gt(BaseMetadata): + """Gt(gt=x) implies that the value must be greater than x. + + It can be used with any type that supports the ``>`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + gt: SupportsGt + + +@dataclass(frozen=True, **SLOTS) +class Ge(BaseMetadata): + """Ge(ge=x) implies that the value must be greater than or equal to x. + + It can be used with any type that supports the ``>=`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + ge: SupportsGe + + +@dataclass(frozen=True, **SLOTS) +class Lt(BaseMetadata): + """Lt(lt=x) implies that the value must be less than x. + + It can be used with any type that supports the ``<`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + lt: SupportsLt + + +@dataclass(frozen=True, **SLOTS) +class Le(BaseMetadata): + """Le(le=x) implies that the value must be less than or equal to x. + + It can be used with any type that supports the ``<=`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + le: SupportsLe + + +@runtime_checkable +class GroupedMetadata(Protocol): + """A grouping of multiple objects, like typing.Unpack. + + `GroupedMetadata` on its own is not metadata and has no meaning. + All of the constraints and metadata should be fully expressable + in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`. + + Concrete implementations should override `GroupedMetadata.__iter__()` + to add their own metadata. + For example: + + >>> @dataclass + >>> class Field(GroupedMetadata): + >>> gt: float | None = None + >>> description: str | None = None + ... + >>> def __iter__(self) -> Iterable[object]: + >>> if self.gt is not None: + >>> yield Gt(self.gt) + >>> if self.description is not None: + >>> yield Description(self.gt) + + Also see the implementation of `Interval` below for an example. + + Parsers should recognize this and unpack it so that it can be used + both with and without unpacking: + + - `Annotated[int, Field(...)]` (parser must unpack Field) + - `Annotated[int, *Field(...)]` (PEP-646) + """ # noqa: trailing-whitespace + + @property + def __is_annotated_types_grouped_metadata__(self) -> Literal[True]: + return True + + def __iter__(self) -> Iterator[object]: + ... + + if not TYPE_CHECKING: + __slots__ = () # allow subclasses to use slots + + def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None: + # Basic ABC like functionality without the complexity of an ABC + super().__init_subclass__(*args, **kwargs) + if cls.__iter__ is GroupedMetadata.__iter__: + raise TypeError("Can't subclass GroupedMetadata without implementing __iter__") + + def __iter__(self) -> Iterator[object]: # noqa: F811 + raise NotImplementedError # more helpful than "None has no attribute..." type errors + + +@dataclass(frozen=True, **KW_ONLY, **SLOTS) +class Interval(GroupedMetadata): + """Interval can express inclusive or exclusive bounds with a single object. + + It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which + are interpreted the same way as the single-bound constraints. + """ + + gt: Union[SupportsGt, None] = None + ge: Union[SupportsGe, None] = None + lt: Union[SupportsLt, None] = None + le: Union[SupportsLe, None] = None + + def __iter__(self) -> Iterator[BaseMetadata]: + """Unpack an Interval into zero or more single-bounds.""" + if self.gt is not None: + yield Gt(self.gt) + if self.ge is not None: + yield Ge(self.ge) + if self.lt is not None: + yield Lt(self.lt) + if self.le is not None: + yield Le(self.le) + + +@dataclass(frozen=True, **SLOTS) +class MultipleOf(BaseMetadata): + """MultipleOf(multiple_of=x) might be interpreted in two ways: + + 1. Python semantics, implying ``value % multiple_of == 0``, or + 2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of`` + + We encourage users to be aware of these two common interpretations, + and libraries to carefully document which they implement. + """ + + multiple_of: Union[SupportsDiv, SupportsMod] + + +@dataclass(frozen=True, **SLOTS) +class MinLen(BaseMetadata): + """ + MinLen() implies minimum inclusive length, + e.g. ``len(value) >= min_length``. + """ + + min_length: Annotated[int, Ge(0)] + + +@dataclass(frozen=True, **SLOTS) +class MaxLen(BaseMetadata): + """ + MaxLen() implies maximum inclusive length, + e.g. ``len(value) <= max_length``. + """ + + max_length: Annotated[int, Ge(0)] + + +@dataclass(frozen=True, **SLOTS) +class Len(GroupedMetadata): + """ + Len() implies that ``min_length <= len(value) <= max_length``. + + Upper bound may be omitted or ``None`` to indicate no upper length bound. + """ + + min_length: Annotated[int, Ge(0)] = 0 + max_length: Optional[Annotated[int, Ge(0)]] = None + + def __iter__(self) -> Iterator[BaseMetadata]: + """Unpack a Len into zone or more single-bounds.""" + if self.min_length > 0: + yield MinLen(self.min_length) + if self.max_length is not None: + yield MaxLen(self.max_length) + + +@dataclass(frozen=True, **SLOTS) +class Timezone(BaseMetadata): + """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive). + + ``Annotated[datetime, Timezone(None)]`` must be a naive datetime. + ``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be + tz-aware but any timezone is allowed. + + You may also pass a specific timezone string or tzinfo object such as + ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that + you only allow a specific timezone, though we note that this is often + a symptom of poor design. + """ + + tz: Union[str, tzinfo, EllipsisType, None] + + +@dataclass(frozen=True, **SLOTS) +class Unit(BaseMetadata): + """Indicates that the value is a physical quantity with the specified unit. + + It is intended for usage with numeric types, where the value represents the + magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]`` + or ``speed: Annotated[float, Unit('m/s')]``. + + Interpretation of the unit string is left to the discretion of the consumer. + It is suggested to follow conventions established by python libraries that work + with physical quantities, such as + + - ``pint`` : + - ``astropy.units``: + + For indicating a quantity with a certain dimensionality but without a specific unit + it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`. + Note, however, ``annotated_types`` itself makes no use of the unit string. + """ + + unit: str + + +@dataclass(frozen=True, **SLOTS) +class Predicate(BaseMetadata): + """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values. + + Users should prefer statically inspectable metadata, but if you need the full + power and flexibility of arbitrary runtime predicates... here it is. + + We provide a few predefined predicates for common string constraints: + ``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and + ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which + can be given special handling, and avoid indirection like ``lambda s: s.lower()``. + + Some libraries might have special logic to handle certain predicates, e.g. by + checking for `str.isdigit` and using its presence to both call custom logic to + enforce digit-only strings, and customise some generated external schema. + + We do not specify what behaviour should be expected for predicates that raise + an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently + skip invalid constraints, or statically raise an error; or it might try calling it + and then propagate or discard the resulting exception. + """ + + func: Callable[[Any], bool] + + def __repr__(self) -> str: + if getattr(self.func, "__name__", "") == "": + return f"{self.__class__.__name__}({self.func!r})" + if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and ( + namespace := getattr(self.func.__self__, "__name__", None) + ): + return f"{self.__class__.__name__}({namespace}.{self.func.__name__})" + if isinstance(self.func, type(str.isascii)): # method descriptor + return f"{self.__class__.__name__}({self.func.__qualname__})" + return f"{self.__class__.__name__}({self.func.__name__})" + + +@dataclass +class Not: + func: Callable[[Any], bool] + + def __call__(self, __v: Any) -> bool: + return not self.func(__v) + + +_StrType = TypeVar("_StrType", bound=str) + +LowerCase = Annotated[_StrType, Predicate(str.islower)] +""" +Return True if the string is a lowercase string, False otherwise. + +A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string. +""" # noqa: E501 +UpperCase = Annotated[_StrType, Predicate(str.isupper)] +""" +Return True if the string is an uppercase string, False otherwise. + +A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string. +""" # noqa: E501 +IsDigit = Annotated[_StrType, Predicate(str.isdigit)] +IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63 +""" +Return True if the string is a digit string, False otherwise. + +A string is a digit string if all characters in the string are digits and there is at least one character in the string. +""" # noqa: E501 +IsAscii = Annotated[_StrType, Predicate(str.isascii)] +""" +Return True if all characters in the string are ASCII, False otherwise. + +ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too. +""" + +_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex]) +IsFinite = Annotated[_NumericType, Predicate(math.isfinite)] +"""Return True if x is neither an infinity nor a NaN, and False otherwise.""" +IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))] +"""Return True if x is one of infinity or NaN, and False otherwise""" +IsNan = Annotated[_NumericType, Predicate(math.isnan)] +"""Return True if x is a NaN (not a number), and False otherwise.""" +IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))] +"""Return True if x is anything but NaN (not a number), and False otherwise.""" +IsInfinite = Annotated[_NumericType, Predicate(math.isinf)] +"""Return True if x is a positive or negative infinity, and False otherwise.""" +IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))] +"""Return True if x is neither a positive or negative infinity, and False otherwise.""" + +try: + from typing_extensions import DocInfo, doc # type: ignore [attr-defined] +except ImportError: + + @dataclass(frozen=True, **SLOTS) + class DocInfo: # type: ignore [no-redef] + """ " + The return value of doc(), mainly to be used by tools that want to extract the + Annotated documentation at runtime. + """ + + documentation: str + """The documentation string passed to doc().""" + + def doc( + documentation: str, + ) -> DocInfo: + """ + Add documentation to a type annotation inside of Annotated. + + For example: + + >>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ... + """ + return DocInfo(documentation) diff --git a/.venv/Lib/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64dd8b69440f41fb92251ee2d98e741ea0f7f144 GIT binary patch literal 18643 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/annotated_types/py.typed b/.venv/Lib/site-packages/annotated_types/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/annotated_types/test_cases.py b/.venv/Lib/site-packages/annotated_types/test_cases.py new file mode 100644 index 0000000..d9164d6 --- /dev/null +++ b/.venv/Lib/site-packages/annotated_types/test_cases.py @@ -0,0 +1,151 @@ +import math +import sys +from datetime import date, datetime, timedelta, timezone +from decimal import Decimal +from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple + +if sys.version_info < (3, 9): + from typing_extensions import Annotated +else: + from typing import Annotated + +import annotated_types as at + + +class Case(NamedTuple): + """ + A test case for `annotated_types`. + """ + + annotation: Any + valid_cases: Iterable[Any] + invalid_cases: Iterable[Any] + + +def cases() -> Iterable[Case]: + # Gt, Ge, Lt, Le + yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1)) + yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1)) + yield Case( + Annotated[datetime, at.Gt(datetime(2000, 1, 1))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(2000, 1, 1), datetime(1999, 12, 31)], + ) + yield Case( + Annotated[datetime, at.Gt(date(2000, 1, 1))], + [date(2000, 1, 2), date(2000, 1, 3)], + [date(2000, 1, 1), date(1999, 12, 31)], + ) + yield Case( + Annotated[datetime, at.Gt(Decimal('1.123'))], + [Decimal('1.1231'), Decimal('123')], + [Decimal('1.123'), Decimal('0')], + ) + + yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1)) + yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1)) + yield Case( + Annotated[datetime, at.Ge(datetime(2000, 1, 1))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(1998, 1, 1), datetime(1999, 12, 31)], + ) + + yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4)) + yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9)) + yield Case( + Annotated[datetime, at.Lt(datetime(2000, 1, 1))], + [datetime(1999, 12, 31), datetime(1999, 12, 31)], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + ) + + yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000)) + yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9)) + yield Case( + Annotated[datetime, at.Le(datetime(2000, 1, 1))], + [datetime(2000, 1, 1), datetime(1999, 12, 31)], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + ) + + # Interval + yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1)) + yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1)) + yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1)) + yield Case( + Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(2000, 1, 1), datetime(2000, 1, 4)], + ) + + yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4)) + yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1)) + + # lengths + + yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12')) + yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12')) + yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2])) + yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2])) + + yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10)) + yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10)) + yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10)) + yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10)) + + yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10)) + yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234')) + + yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}]) + yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4})) + yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4))) + + # Timezone + + yield Case( + Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)] + ) + yield Case( + Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)] + ) + yield Case( + Annotated[datetime, at.Timezone(timezone.utc)], + [datetime(2000, 1, 1, tzinfo=timezone.utc)], + [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))], + ) + yield Case( + Annotated[datetime, at.Timezone('Europe/London')], + [datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))], + [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))], + ) + + # Quantity + + yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m')) + + # predicate types + + yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom']) + yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC']) + yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2']) + yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀']) + + yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5]) + + yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf]) + yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23]) + yield Case(at.IsNan[float], [math.nan], [1.23, math.inf]) + yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan]) + yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23]) + yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf]) + + # check stacked predicates + yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan]) + + # doc + yield Case(Annotated[int, at.doc("A number")], [1, 2], []) + + # custom GroupedMetadata + class MyCustomGroupedMetadata(at.GroupedMetadata): + def __iter__(self) -> Iterator[at.Predicate]: + yield at.Predicate(lambda x: float(x).is_integer()) + + yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5]) diff --git a/.venv/Lib/site-packages/anyio-4.9.0.dist-info/INSTALLER b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/anyio-4.9.0.dist-info/LICENSE b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/LICENSE new file mode 100644 index 0000000..104eebf --- /dev/null +++ b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Alex Grönholm + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.venv/Lib/site-packages/anyio-4.9.0.dist-info/METADATA b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/METADATA new file mode 100644 index 0000000..9d87e1d --- /dev/null +++ b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/METADATA @@ -0,0 +1,105 @@ +Metadata-Version: 2.2 +Name: anyio +Version: 4.9.0 +Summary: High level compatibility layer for multiple asynchronous event loop implementations +Author-email: Alex Grönholm +License: MIT +Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/ +Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html +Project-URL: Source code, https://github.com/agronholm/anyio +Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Framework :: AnyIO +Classifier: Typing :: Typed +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Requires-Python: >=3.9 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11" +Requires-Dist: idna>=2.8 +Requires-Dist: sniffio>=1.1 +Requires-Dist: typing_extensions>=4.5; python_version < "3.13" +Provides-Extra: trio +Requires-Dist: trio>=0.26.1; extra == "trio" +Provides-Extra: test +Requires-Dist: anyio[trio]; extra == "test" +Requires-Dist: blockbuster>=1.5.23; extra == "test" +Requires-Dist: coverage[toml]>=7; extra == "test" +Requires-Dist: exceptiongroup>=1.2.0; extra == "test" +Requires-Dist: hypothesis>=4.0; extra == "test" +Requires-Dist: psutil>=5.9; extra == "test" +Requires-Dist: pytest>=7.0; extra == "test" +Requires-Dist: trustme; extra == "test" +Requires-Dist: truststore>=0.9.1; python_version >= "3.10" and extra == "test" +Requires-Dist: uvloop>=0.21; (platform_python_implementation == "CPython" and platform_system != "Windows" and python_version < "3.14") and extra == "test" +Provides-Extra: doc +Requires-Dist: packaging; extra == "doc" +Requires-Dist: Sphinx~=8.2; extra == "doc" +Requires-Dist: sphinx_rtd_theme; extra == "doc" +Requires-Dist: sphinx-autodoc-typehints>=1.2.0; extra == "doc" + +.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg + :target: https://github.com/agronholm/anyio/actions/workflows/test.yml + :alt: Build Status +.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master + :target: https://coveralls.io/github/agronholm/anyio?branch=master + :alt: Code Coverage +.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest + :target: https://anyio.readthedocs.io/en/latest/?badge=latest + :alt: Documentation +.. image:: https://badges.gitter.im/gitterHQ/gitter.svg + :target: https://gitter.im/python-trio/AnyIO + :alt: Gitter chat + +AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or +trio_. It implements trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony +with the native SC of trio itself. + +Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or +trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full +refactoring necessary. It will blend in with the native libraries of your chosen backend. + +Documentation +------------- + +View full documentation at: https://anyio.readthedocs.io/ + +Features +-------- + +AnyIO offers the following functionality: + +* Task groups (nurseries_ in trio terminology) +* High-level networking (TCP, UDP and UNIX sockets) + + * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python + 3.8) + * async/await style UDP sockets (unlike asyncio where you still have to use Transports and + Protocols) + +* A versatile API for byte streams and object streams +* Inter-task synchronization and communication (locks, conditions, events, semaphores, object + streams) +* Worker threads +* Subprocesses +* Asynchronous file I/O (using worker threads) +* Signal handling + +AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures. +It even works with the popular Hypothesis_ library. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _trio: https://github.com/python-trio/trio +.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency +.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning +.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs +.. _pytest: https://docs.pytest.org/en/latest/ +.. _Hypothesis: https://hypothesis.works/ diff --git a/.venv/Lib/site-packages/anyio-4.9.0.dist-info/RECORD b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/RECORD new file mode 100644 index 0000000..971925e --- /dev/null +++ b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/RECORD @@ -0,0 +1,88 @@ +anyio-4.9.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +anyio-4.9.0.dist-info/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081 +anyio-4.9.0.dist-info/METADATA,sha256=vvkWPXXTbrpTCFK7zdcYwQcSQhx6Q4qITM9t_PEQCrY,4682 +anyio-4.9.0.dist-info/RECORD,, +anyio-4.9.0.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91 +anyio-4.9.0.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39 +anyio-4.9.0.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6 +anyio/__init__.py,sha256=t8bZuNXa5ncwXBaNKbv48BDgZt48RT_zCEtrnPmjNU8,4993 +anyio/__pycache__/__init__.cpython-312.pyc,, +anyio/__pycache__/from_thread.cpython-312.pyc,, +anyio/__pycache__/lowlevel.cpython-312.pyc,, +anyio/__pycache__/pytest_plugin.cpython-312.pyc,, +anyio/__pycache__/to_interpreter.cpython-312.pyc,, +anyio/__pycache__/to_process.cpython-312.pyc,, +anyio/__pycache__/to_thread.cpython-312.pyc,, +anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_backends/__pycache__/__init__.cpython-312.pyc,, +anyio/_backends/__pycache__/_asyncio.cpython-312.pyc,, +anyio/_backends/__pycache__/_trio.cpython-312.pyc,, +anyio/_backends/_asyncio.py,sha256=AT1oaTfCE-9YFxooMlvld2yDqY5U2A-ANMcBDh9eRfI,93455 +anyio/_backends/_trio.py,sha256=HVfDqRGQ7Xj3JfTcYdgzmC7pZEplqU4NOO5kxNNSZnk,40429 +anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_core/__pycache__/__init__.cpython-312.pyc,, +anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc,, +anyio/_core/__pycache__/_eventloop.cpython-312.pyc,, +anyio/_core/__pycache__/_exceptions.cpython-312.pyc,, +anyio/_core/__pycache__/_fileio.cpython-312.pyc,, +anyio/_core/__pycache__/_resources.cpython-312.pyc,, +anyio/_core/__pycache__/_signals.cpython-312.pyc,, +anyio/_core/__pycache__/_sockets.cpython-312.pyc,, +anyio/_core/__pycache__/_streams.cpython-312.pyc,, +anyio/_core/__pycache__/_subprocesses.cpython-312.pyc,, +anyio/_core/__pycache__/_synchronization.cpython-312.pyc,, +anyio/_core/__pycache__/_tasks.cpython-312.pyc,, +anyio/_core/__pycache__/_tempfile.cpython-312.pyc,, +anyio/_core/__pycache__/_testing.cpython-312.pyc,, +anyio/_core/__pycache__/_typedattr.cpython-312.pyc,, +anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626 +anyio/_core/_eventloop.py,sha256=t_tAwBFPjF8jrZGjlJ6bbYy6KA3bjsbZxV9mvh9t1i0,4695 +anyio/_core/_exceptions.py,sha256=RlPRlwastdmfDPoskdXNO6SI8_l3fclA2wtW6cokU9I,3503 +anyio/_core/_fileio.py,sha256=qFZhkLIz0cGXluvih_vcPUTucgq8UFVgsTCtYbijZIg,23340 +anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435 +anyio/_core/_signals.py,sha256=vulT1M1xdLYtAR-eY5TamIgaf1WTlOwOrMGwswlTTr8,905 +anyio/_core/_sockets.py,sha256=5Okc_UThGDEN9KCnsIhqWPRHBNuSy6b4NmG1i51TVF4,27150 +anyio/_core/_streams.py,sha256=OnaKgoDD-FcMSwLvkoAUGP51sG2ZdRvMpxt9q2w1gYA,1804 +anyio/_core/_subprocesses.py,sha256=EXm5igL7dj55iYkPlbYVAqtbqxJxjU-6OndSTIx9SRg,8047 +anyio/_core/_synchronization.py,sha256=DwUh8Tl6cG_UMVC_GyzPoC_U9BpfDfjMl9SINSxcZN4,20320 +anyio/_core/_tasks.py,sha256=f3CuWwo06cCZ6jaOv-JHFKWkgpgf2cvaF25Oh4augMA,4757 +anyio/_core/_tempfile.py,sha256=s-_ucacXbxBH5Bo5eo65lN0lPwZQd5B8yNN_9nARpCM,19696 +anyio/_core/_testing.py,sha256=YUGwA5cgFFbUTv4WFd7cv_BSVr4ryTtPp8owQA3JdWE,2118 +anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508 +anyio/abc/__init__.py,sha256=c2OQbTCS_fQowviMXanLPh8m29ccwkXmpDr7uyNZYOo,2652 +anyio/abc/__pycache__/__init__.cpython-312.pyc,, +anyio/abc/__pycache__/_eventloop.cpython-312.pyc,, +anyio/abc/__pycache__/_resources.cpython-312.pyc,, +anyio/abc/__pycache__/_sockets.cpython-312.pyc,, +anyio/abc/__pycache__/_streams.cpython-312.pyc,, +anyio/abc/__pycache__/_subprocesses.cpython-312.pyc,, +anyio/abc/__pycache__/_tasks.cpython-312.pyc,, +anyio/abc/__pycache__/_testing.cpython-312.pyc,, +anyio/abc/_eventloop.py,sha256=UmL8DZCvQTgxzmyBZcGm9kWj9VQY8BMWueLh5S8yWN4,9682 +anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783 +anyio/abc/_sockets.py,sha256=KhWtJxan8jpBXKwPaFeQzI4iRXdFaOIn0HXtDZnaO7U,6262 +anyio/abc/_streams.py,sha256=He_JpkAW2g5veOzcUq0XsRC2nId_i35L-d8cs7Uj1ZQ,6598 +anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067 +anyio/abc/_tasks.py,sha256=yJWbMwowvqjlAX4oJ3l9Is1w-zwynr2lX1Z02AWJqsY,3080 +anyio/abc/_testing.py,sha256=tBJUzkSfOXJw23fe8qSJ03kJlShOYjjaEyFB6k6MYT8,1821 +anyio/from_thread.py,sha256=MbXHZpgM9wgsRkbGhMNMomEGYj7Y_QYq6a5BZ3c5Ev8,17478 +anyio/lowlevel.py,sha256=nkgmW--SdxGVp0cmLUYazjkigveRm5HY7-gW8Bpp9oY,4169 +anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/pytest_plugin.py,sha256=qXNwk9Pa7hPQKWocgLl9qijqKGMkGzdH2wJa-jPkGUM,9375 +anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/streams/__pycache__/__init__.cpython-312.pyc,, +anyio/streams/__pycache__/buffered.cpython-312.pyc,, +anyio/streams/__pycache__/file.cpython-312.pyc,, +anyio/streams/__pycache__/memory.cpython-312.pyc,, +anyio/streams/__pycache__/stapled.cpython-312.pyc,, +anyio/streams/__pycache__/text.cpython-312.pyc,, +anyio/streams/__pycache__/tls.cpython-312.pyc,, +anyio/streams/buffered.py,sha256=UCldKC168YuLvT7n3HtNPnQ2iWAMSTYQWbZvzLwMwkM,4500 +anyio/streams/file.py,sha256=6uoTNb5KbMoj-6gS3_xrrL8uZN8Q4iIvOS1WtGyFfKw,4383 +anyio/streams/memory.py,sha256=o1OVVx0OooteTTe2GytJreum93Ucuw5s4cAsr3X0-Ag,10560 +anyio/streams/stapled.py,sha256=U09pCrmOw9kkNhe6tKopsm1QIMT1lFTFvtb-A7SIe4k,4302 +anyio/streams/text.py,sha256=6x8w8xlfCZKTUWQoJiMPoMhSSJFUBRKgoBNSBtbd9yg,5094 +anyio/streams/tls.py,sha256=HxzpVmUgo8SUSIBass_lvef1pAI1uRSrnysM3iEGzl4,13199 +anyio/to_interpreter.py,sha256=UhuNCIucCRN7ZtyJg35Mlamzs1JpgDvK4xnL4TDWrAo,6527 +anyio/to_process.py,sha256=ZvruelRM-HNmqDaql4sdNODg2QD_uSlwSCxnV4OhsfQ,9595 +anyio/to_thread.py,sha256=WM2JQ2MbVsd5D5CM08bQiTwzZIvpsGjfH1Fy247KoDQ,2396 diff --git a/.venv/Lib/site-packages/anyio-4.9.0.dist-info/WHEEL b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/WHEEL new file mode 100644 index 0000000..9c3ae63 --- /dev/null +++ b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (76.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/Lib/site-packages/anyio-4.9.0.dist-info/entry_points.txt b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/entry_points.txt new file mode 100644 index 0000000..44dd9bd --- /dev/null +++ b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +anyio = anyio.pytest_plugin diff --git a/.venv/Lib/site-packages/anyio-4.9.0.dist-info/top_level.txt b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/top_level.txt new file mode 100644 index 0000000..c77c069 --- /dev/null +++ b/.venv/Lib/site-packages/anyio-4.9.0.dist-info/top_level.txt @@ -0,0 +1 @@ +anyio diff --git a/.venv/Lib/site-packages/anyio/__init__.py b/.venv/Lib/site-packages/anyio/__init__.py new file mode 100644 index 0000000..578cda6 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/__init__.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +from ._core._eventloop import current_time as current_time +from ._core._eventloop import get_all_backends as get_all_backends +from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class +from ._core._eventloop import run as run +from ._core._eventloop import sleep as sleep +from ._core._eventloop import sleep_forever as sleep_forever +from ._core._eventloop import sleep_until as sleep_until +from ._core._exceptions import BrokenResourceError as BrokenResourceError +from ._core._exceptions import BrokenWorkerIntepreter as BrokenWorkerIntepreter +from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess +from ._core._exceptions import BusyResourceError as BusyResourceError +from ._core._exceptions import ClosedResourceError as ClosedResourceError +from ._core._exceptions import DelimiterNotFound as DelimiterNotFound +from ._core._exceptions import EndOfStream as EndOfStream +from ._core._exceptions import IncompleteRead as IncompleteRead +from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError +from ._core._exceptions import WouldBlock as WouldBlock +from ._core._fileio import AsyncFile as AsyncFile +from ._core._fileio import Path as Path +from ._core._fileio import open_file as open_file +from ._core._fileio import wrap_file as wrap_file +from ._core._resources import aclose_forcefully as aclose_forcefully +from ._core._signals import open_signal_receiver as open_signal_receiver +from ._core._sockets import connect_tcp as connect_tcp +from ._core._sockets import connect_unix as connect_unix +from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket +from ._core._sockets import ( + create_connected_unix_datagram_socket as create_connected_unix_datagram_socket, +) +from ._core._sockets import create_tcp_listener as create_tcp_listener +from ._core._sockets import create_udp_socket as create_udp_socket +from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket +from ._core._sockets import create_unix_listener as create_unix_listener +from ._core._sockets import getaddrinfo as getaddrinfo +from ._core._sockets import getnameinfo as getnameinfo +from ._core._sockets import wait_readable as wait_readable +from ._core._sockets import wait_socket_readable as wait_socket_readable +from ._core._sockets import wait_socket_writable as wait_socket_writable +from ._core._sockets import wait_writable as wait_writable +from ._core._streams import create_memory_object_stream as create_memory_object_stream +from ._core._subprocesses import open_process as open_process +from ._core._subprocesses import run_process as run_process +from ._core._synchronization import CapacityLimiter as CapacityLimiter +from ._core._synchronization import ( + CapacityLimiterStatistics as CapacityLimiterStatistics, +) +from ._core._synchronization import Condition as Condition +from ._core._synchronization import ConditionStatistics as ConditionStatistics +from ._core._synchronization import Event as Event +from ._core._synchronization import EventStatistics as EventStatistics +from ._core._synchronization import Lock as Lock +from ._core._synchronization import LockStatistics as LockStatistics +from ._core._synchronization import ResourceGuard as ResourceGuard +from ._core._synchronization import Semaphore as Semaphore +from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics +from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED +from ._core._tasks import CancelScope as CancelScope +from ._core._tasks import create_task_group as create_task_group +from ._core._tasks import current_effective_deadline as current_effective_deadline +from ._core._tasks import fail_after as fail_after +from ._core._tasks import move_on_after as move_on_after +from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile +from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile +from ._core._tempfile import TemporaryDirectory as TemporaryDirectory +from ._core._tempfile import TemporaryFile as TemporaryFile +from ._core._tempfile import gettempdir as gettempdir +from ._core._tempfile import gettempdirb as gettempdirb +from ._core._tempfile import mkdtemp as mkdtemp +from ._core._tempfile import mkstemp as mkstemp +from ._core._testing import TaskInfo as TaskInfo +from ._core._testing import get_current_task as get_current_task +from ._core._testing import get_running_tasks as get_running_tasks +from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked +from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider +from ._core._typedattr import TypedAttributeSet as TypedAttributeSet +from ._core._typedattr import typed_attribute as typed_attribute + +# Re-export imports so they look like they live directly in this package +for __value in list(locals().values()): + if getattr(__value, "__module__", "").startswith("anyio."): + __value.__module__ = __name__ + +del __value diff --git a/.venv/Lib/site-packages/anyio/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34fd4278d9d22b9f5d76e3ea8f936372cdfeae70 GIT binary patch literal 3714 zcmZ{m$x|H36~?m>&@A2TLIMd1EeN3nZD>P?eU%U}G+LbMajB?EP>EVPvZ|4KPIiP3 zbC}D7596b+6T0|6@X4n+O-#s3N7Ts&$J}OOVlF=Ud#}5iLAHtJ>tDY2a;eOhuj`v^ zwngCYYis=VZ@Y!~CpD74#tNigri+Z$U0F z-vckoMdth9CAq{r4qlea%n!gTa)o&U{761xe&{~-R^=-5B>0JZ!aN0DlWWY=;B~pq zJOkd48_cudPvj@ebKp(6$vh9v$sF^7yX8HVPnj3N&*U@aCGd0koOv1iLcU;Laew7W zDVZOEU&@!vkHLAFPl-CAKE?m9-&b$xn?EPl_24`4y-!ta^WxKULdaL>XR25Bs{Q2s zk&5TdUk-XDWUnRt!qgW7%2KZkJMn!nR4-n>D%Fjwe@~{7RJqmk{U9l)jpwWoYLgzXsFEYr7?>nht&?<~V|&5&G2bkWCGL+C11 z0Av}Aoj|L7rD15T7>a)6xM+6I>Hx!TsxT;OOD$*}P*iWl{5;TmO0W2lDrgmP|7wSd zXF~^;;(0q~i(%>GQM%{cAXN6p=FWNLI-Y~^)`Dm;DEc<7Yr(hIcXE+driW(Rif;v8 z!Nshb%A|n4tx`eRGf||S?P8=>gJ7>%NY>l(JSe*MtQ%N+2yLDTOTM+}xU_-w8)o#H zj6p&9#!ki9{8pQVq|s?wv`*S2OYIb0w}kK>4iBAO-*gR)qjakKX9d2mES!%;`@Ce=$}ovdFY;?f5$ZmPSqA^i;K(eQP=q9^ACeZQjQoHdLPE|JvVb zC#uF*2Ka>`S*EAFK$nbQ`weYln4EZqH@r~Gs8(dManNj^GYh8WM5WbA>R`ovADMD= zm%_-g=pr=F1-|Xj-9n?g);JW?u&|Gt27f!lAz%G!C70Uh`;aG_W3v;n7TvH!{OLV^;@7O0oW= zBB-4m>=^RUuyHJ|<5QQG9n*0QbEk4Uvt9sq;MWXYYdBJSOL>Jro4QoJ6Fs>?5TJB? zZ11WW<{jjGgmc0a*_yT5y#=WfVMyEIigM7}Ms(WSv&mUOWG-XV79MG(l4)Ghl82&4 zMQA4mF>vC(hxQ%2azhS#a(wODBdQ&SnG}z5 zi(zSB-a$lG|3FfEXf?nJjnv2 z_MDU!NP4|Uo+XlHk`;>vEG}P@PAHKfw@Ri=Uw4-;On&e~MHN^EuPY&#Q1R3JUogZq*=e z7nSNTzi5Wh%*M*#emJ;@_l>>$GQE@N!TI1#kRL@l@8?&Y?R=1Q_*M;)Eab zAP^q_>RSiV0pDwq1SC*(zRINgN_J8 z$JM0c^pB0Aqvy-T-!Ff){^k02JwtzOzwn!-f27+!fBT2bAGg0tkNhn?_HBCXPcJ^C cC;vV$^m+P2Q~wVQqGR~SCee`5d)T%A0KPJ1E&u=k literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/anyio/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc79684b8eaf7c40bc6c9029cf7ff391ecbbb629 GIT binary patch literal 3600 zcmZ{m$x|Fh7RIyLnyuLdLI?>V0YVEx5|R*$*qTisz%-H^X*?B0Rf0;?(vek-gikX! zAAE7l!9LE_q02?G&n8O%(dWJS<74pu9J1l^=`e_ARCw)z>Ttzxe=U^8RiVQNj5PzftzJB zb2GR_wlKGVTV*SAE4WRzF}H!+Wjk{_xI=a@cev-gPT9$P4%{WXm^;De<$2~VaJTGc zKJQ-edSnlCw|mj+mA%Xt+)LhNd6~Hf+$Z~(FM_YgE6ly%e%a4_34B#vWxfo)Ca*E~ zfd}LO^A+%*9AxeX56L0stKjSMI`cK~upDL{0N;=|mejpz(kANS_hs<}tkK`lfQSi7NXTA%5 zEFUx915d~a=KJ7DImtW*o|03{55UuMn)#vo#G8>b%#Xmca+Y}M8oVr*nV*1Hj1-S9T$Ci5bAOKveQfj^QTF)xF+z*oAY+$4)ZGbxqQz26#PQINQnxeUgH1b zcja7s{ih@;Prno2yHv@R6`x)eLQ0JLL`i$iUVn$YrO)d>m%iUu`&lwBOnuR-K2_5A zP2&Gzph|oqk2i)let4YRzYj!M@Hm$3hkBy6&a@M8hfU7 zsC+wwt&41y>08Qmm2IfEmSMSONUlm<@bOh0x=Q5%83toN(CSEO7#d23f*(09n(gyC zz_i;c3<}y(i&_WNtFvT&8R$c$SN%xkwTgIrc|ggtr2|Xxx~=nt@c6@7+85j)RQ8AF z)~s?J&%t~fL9`SUe4F;Q=-Zq7xkxM1L$hhsw*oKkV%2SBQitxH}9P;l*eH?R)TwQepv_N^tyr30+mGNV^y4D!l1_DjaPH`>f6jaJj5ebOOW zYQNyR$LQX{-9zWVH(f(3OF8B9vjX2&7VgKQ^KDF;1>bp#c8`Szh?G$_<8}&m-UzWT z6~R65-)@XEY%?+swCSC8Y%h1jDu(NXk@ETUTg&D%lO}7@7XHUn9myDSzc*I{qR6yu z?fClvwnk0g^i;W-d1E>e9^ACeJwC?{Hk6*^|JvVZCo1 zFuCzYKJffWL^U9ijniiHf|)lhCpum)g${Py{edY*sT4+zMTtct~u8vM-+XM9!brC4gB?=zlE63@#8Q&VKt<&|R zXRI!7Y;G@R(Ys-Rvp8qr;n7U4H!{OR<3I-m3b6}GMNs?uI5EVbVdGj{$EP9n`=;X> z=6)$VGhP69;GY=UHgKi(j`H$>HuZ5iCpvQZAVBH((B5`p$U2Dm2={~~GAGvZ@Op$w zgeh%@E6QnW553dgp-s*bB61mPckoE7rAXtFmOKM^rluGbtX$dc*JnyWlFrD2mT&O#j#kMmB~G za_kqPf`&s=oiDZ^m9-i%P(J<04T8L`hFQ~FoKBO}lGKsVJD+ZV6kC%)Z!MMQ@~#`n znIUN+X(nkQX(eeR!JDGc9VF)<#Y}k;Dp^jTJIT>Sa-O7{x+I&^g1#;S>5A$pFb9$q>nPl3~c{Qk=%2zCp(JWc|Wk zIe}HEZ_=1sN04G;c{+{} zJx=z=Boic)BvT~QkYaPv7g0&)s-KW+hGdpxj%1!>fh0?^2swGqNTI;^2YQJ-%Ooo# ztJEjC4GuPnHT{%)Yb5K`p^B<+u?A6Wx?!>A$aD)TET#>EshCC%(6C~C7@0Z>-#F2$ zVy$6NRYo{I!xZ0$U!daO&cej*^HAw#hvtC_cR3U87>47c`Wqwp<6`Yp zDN~;41FTGMDIs2!#ZQ%~R4Nwz{}dCkn2*I)EUw4mRxGAsF&>N6SZuN`Ci*xQGqHFc zi$}4z8;c=UKsFXDv3SU%Ui?~7k*fH$Mx>T8H)eg2IN`@S=!ic6?9;KBjK$K)2sAcg zaSx_gjPkS?kFGFciLqGB#$ua0V0$OK!l(zlr8}{}9|ocu2CgRqXMe5{EgfGj{eI=E z%`Z2z>% literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/__pycache__/from_thread.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/__pycache__/from_thread.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8c8f45479e2f714675335501b5b015eddb16604 GIT binary patch literal 25292 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 AN&o-= literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/__pycache__/lowlevel.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/__pycache__/lowlevel.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ca29afeebc6495206e8bc9469bf2d96c2334361 GIT binary patch literal 7101 zcmcgxU2q%Mb-ue;fcOXaCs5*#R+K~$6b(g|ZP}1yi54Z9F+)2hEmaUTnC!2 zId=gNgqYfXXjjD9`*ZHSd++(q-__p*0$u{ogJgVeEKJD1V5RoB?1E6469`!)ZxD@W zLYicRgdkv9Op6Hze@hu>QWQwWm1wEh(QZkLJBCB_i`FrBoL#`Vmj-C(UTCiO9pHWYps)LhzW#z3?fV^0 z5)~|;n$7B_YNqt8F#&atrkZLptr|wuX*ou+1*_!}%~F<1S}o%#lQK1JwM-Rq^s>q< zw|;}Nw61C}u=NU67ha)-(Nq#AQ(576c&OO(CN&k+C}fk$jGA1aSpHO>n zQ@02aDz8D|6(Rr>%&JgeTUD<4>eg$6@LlV>1+e);=>LJZ>i83<6J{s~C&|0cUpf|r zs93CcWq8id(d2?Wt1~&NW|K6X<{OvQtR}1Jw7w{tb5u5xb5zTxDU(fIHWpK6at=!> zoYNdOs>VXh%cr~W&3x*HnxvQ$TeVz$K?~Tuh?A^ zQIYxKh%E2+xCf+yr}j9Nj8DH|z`*HAH8VX*jRjNBO;4sYD!<0`d73nh>5Hmij=VM| z-!SBhfQW_Zmr;C79@Xdd>DUdLy)iwWnwd5L7Ek4%OMR0X(`vSm(x>4p)AR;S$8rS* z8c#S0Lc!<-@*m{0WwN!8_&DL;5wJJoW z=2C{N>C}(`K3@Q2vX+{irHo@|8)#od$dPR!?*J8Rg(G8aNZOI?sgXV?G>D_ECXQ!I z-e=dnCpV;%A4w-4vkqYUe-*)ha}kVu8GV#d^*~eSba?Y~KnXSL`R~`-oNmI2r@#gj zK39Wr8TmX{%t@5}psk%}rdXav<8=jYP~y6>e<`KiXPbrHl)qV|T_{o{Lpw!U*RbbH z-sji7@eL{dkrdxWr09e|vxLgiQ24TzcM^%YG6lvE>?0U{ zS)W160*a)9nA9w&jGQ;9hSn3D1RsTEw8CILvL>H2t?Rj}=xPQnkjyjG5rt3Nd>s2= z6AVcleg@(ltc{=ed)Iw^8@}O^Z+P7oTa#j3DT)J6IIe5p_J%@QLl?FM5C{GV%<=|!*`@E!FB4jUh#(gSpr zP?8Wdk`N*K5g^(TsiZkDZYW6Lg#$a*h>DiWUc~Y#$|5*jMrRepa@l9gWEeI9quh#; zRWlTNyo!?1(V;=vuPEQltLbWwM^TKlZW_=LQj~^BKv9~{bU#eXkX(YFfhPLftp5c6-7$FW9|fm(|!tsKlUWdzTOmIp*Ea)b!L`dBRx$mkryS&@Zd$Je{QGMAXF& z0y{f|1kI22$CSsVK#chHs43TBm^5hyYn8<&MaT^sc9T<|TOf%6t1(r0)j#xlBYQ z_AKxeJut;(m0g><&Ypw%9f*Z>S>Cy_KcCOis;R` z5Cb`dpIRQ#Q;TFY%`!b^QxtLDBf)w%sNptWh9hL#|-v}4Lp=Lt5<(*a3@GZEA} zc@p8)_V6Skq(!tKz$sh^MY}AQea^*e-1v>B7O1SsaUPP#zwt*&9YT}0Opv${J8zdk zDBl>C>03#fGwpDM8&ou+ET7GyC*;OdwhbvdrU#E9m4wH19p%vR5-!k;9lK&0g1@b_cV5fjD>R`{n%aYJMSuNEM+W~0~oMOm6qB@W% zj5097Stgq`>@jlR@IK9jyy6uYs#ClYb5Oem}J0+!Q5G&&TaO_ueSAA6)Yv z{OsdUC~_Yb7TZ+yO4kci@6aRzaNw6USGxK|pohyFs_~7p2>^ZNJtW<-V8K}*SK4Z6Cya)FwKXyE9nt z)G<*Ef_!9vR!W^oQ@Ou*pr2>{kh5OC}~5)8Dsbzo

gg?Z?;r$8G#d z&>PzGquGtV<0beD9WO`v{xn!_?Og4>+xxTGje!?R@Yni6Inup)^X|=$!iPR_khb_{ z3k-Y=Az)}ixtI6Zgi~2FKR?U#&RAkarU_!)sus^LAG^d9Qcmh~sYtf&6+v z0J=jDT7Ze8=x0y>%eE_qz$^fd{Vah=m`Ch2JutG4ob4{N9vAI~mHKT_6`?mBU|Atr z*li|mj2x4;oP)HPun%!+c>ls5a9eOodi4pO zn*E4M6U8XMu;319``TiLrgexScxDFI8MZ+$$_=b||DmQCRkebB4}xjkRMf-10xMfy z9>&||05eQ>90>;8w$b3wtXmUE@;DYpp})~^SWZR}c>bzG;kEA}(~p4w^nBs(#ovkF zn=1JZeB9Qv*8B2$8)*30BWOSSowK~@?0Va|HUBwmI{nV+AD!ChK2d@{--%7p5$G&; zbZvADlsX3Pn?Eo9w7Ays^roM5?_Y6#H?-*_u@k?VelWc%=@9R z3W*?y9!ky6dm@@}$hpCE2Tt2ZA!ERF#u8qP3btOq0;3vSB_;xl<`^Ub+o6-)a3484 zfx!8-RyTDo`R z10au`m??~cSh2>?NmY4@hey9i4q=bGYLCO*gU?~ljou5K7f-$*HGzo{HY|Yzm$Re^ zJxgnAUxAirYg0yIwJ3_FC*g<&aMU&|k6`sRBt1wlXR+Kl26sNpEZFwcgB@c?CXrl5 z(v;S48;bPf zuGYF?wsXQb%ocEZ%aPOk{m2unIH6wYlboFuf5^Fit6y*)<93)k_S)fHgHNxO5u?$A zc(pK_hbterT40D@%RQ4%rOi~9w81*KlwtKm+k*e7k|*^9#ZGnbYv%23+hMi9k@HRMWDFVc1aM9tz5bD N^>-5gNwCZz@IOuFGX4Mn literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc b/.venv/Lib/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13f06be4c5de4c0683629c362f8c6216679271ae GIT binary patch literal 6988 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 X-JaDk%QmDthE$I7Ujn)Y%0vG*QPI#@ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc b/.venv/Lib/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ca23bca1e997c6775c7ba738e94515c1a96e72d GIT binary patch literal 13378 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 F`7Z}4U~vEd literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/__pycache__/to_process.cpython-312.pyc b/.venv/Lib/site-packages/anyio/__pycache__/to_process.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36d830e7ddf4e001e32dad819dd05e4b042503f2 GIT binary patch literal 11813 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 Lx}5(h%+CJ+=?A^S literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/__pycache__/to_thread.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/__pycache__/to_thread.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa94d370eb090c48ac81987e60efaa0bd65dacc7 GIT binary patch literal 3074 zcmZuz&2JmW6`$oUspW@0NGrBw$MU2$RI^Fhq$py5iR~CRj2KB=BUT!Orm-CEj--{A zJH*UV3RMMgYoqO@&~l1+;frRgVjZarbvoZHx_cyB8Q^y%`PcL_DbA& z^Y!Mv_xrs!`^ETpfxz>LTVHDC3Hcg3!^cVlWA}d`+#??GjDU2EhGC$b2{H{+w^k?X zW(?BFHOA5zyJ5qO8RR>KMnR8RL9tV6l(d`;#yb;@36OK%STNa{YD{U_4$7TMqhb(l zFV78)*lWG~N|O+o2h9c0Hr<%+Jkxl_AR(Sr2oCR!WC$;L#rsCGw{Zme(_U%RKkAKp z8Tgy{*vyls{dr|?@@}p%<5j$=yH?{^KT|!lgUYH#Tx4M=A{O}~RM((yUt&SP+5rc7 zY`)*+zhDwXYmLcp2Q7e3d67pBQ~l6&+RRI9<8zd>(QqBW9A#iyKc4xibsu-aV~{AD zNQB%^h2^o~$X>faHmnU}!~8URKc&+hpwTZu%O5lM%->|QQc!xxyvPOU}GnL2J6Ypx3ur z`vTHxQPI`s8>cztZ8Bou{K9$T@+|zpb`!3hERTE`xPW{DDNIrC^ z-erCPdjEx#2;;h&uInS8|s{NRGe< zePRIu@A@P;DLkHvxSV=2a58c)X|G!|LV`_&uTB#UVKGI9?# zDuQ|%@KqjEGja+BWd&y}iDfaaTuz|}zP$^T9O5T!oR}Hf;`pJHuqm~&nvE?iQ;D+j zP-ARiGAb;Vm=*N9Ww+jZM*--~8?4j3%+*RHy3HFv8+u)eW$s3*d4;Ly;`OU^RnaQ} zWJ>dOyf!4gES5#H1_g1o`KI4)LVEKbYmzdT2@wCr#T=$Syi3kKyaY z@O2IZbr@c^$>S+fI`ObL`>1&CLGj$f;)PA?f;wA$ainS2r49DWAe zy7dAbejMDAGq`x_;1W&E*lG(ANF?Zb5k#twJD|qeuEZ~jI2ZN1n4Xwnv5lWy&{IhoPdP4JoHYm1 zJd6S%x(Q4sZbs|l5>#vj`XHO(Vl2Us9LMo;qSWEwOe zwqNZCuNUz1@+BCp+Fu_FsxeJ}Nvcp1N%hO626}t&^v==6>|A;+&D& zD!e!_q5Z0Oe2|59kRwN*9gLx5lgf!f9;E`AI6c7Cusb)gI}Xz0w+-8P&Cr(rALc%T A6951J literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc b/.venv/Lib/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8090fedbdde068b259647f11eec5c11be8d9f85e GIT binary patch literal 2961 zcmZuz&2JmW6`%d!YDH2Xq@37JVtHB{j#($l2Z|VAYP*IFqefHLh?N$hX)K4kBWb1O z4l%QoLRAIa#%O!!A&0ikq5nYO^bcrHx#uF+2dj$;O^^hwZVcogK~8;dc1bC+SK`i_ zuQxNl-|xNIk0&OI1fCE4`ciY8kk7C(eC*6Hc0UK>E(u6rM5JRh3fIWzF2NVE9f6=a^|Z_ch<^Mm%-iied0d>dt#_ z@?@1ose4_hi*ixTU#VGXg|%56h}eUFpT$0p(o$CHt)*j8wuYZWLU1Jqe!nNg*u4YB zPl*9UZkWG0v|;|*NQUEp%*2Fl{RGyB_C1^lk3o_`W)X5PH?e9UjAa>+d%DLjZOO_n%a3|Dzf7kaUekkhp= zS=!gm_5sprQPI`@LPdFu^aT_U)FTt7 zR_GcFlQ3SS3qsNm-20*fJmgcf-`pJ!UdU$YHDQ0SZmFzd3f+=X0%-}D?g&5wcbXke zAt^CXG}_c@SYJTOfn#OT4fq1nHb+{>?PR32vt77AmPKvLMjYzEo4s1nGh5nts|&6Ly7D680GOH7Mt z^>R)<=o*&B}N7PcKK4h`IZ9G zn>Sded6}z~M0A@sKsNNc6wBOCRPzc`$;Ine>8hevB8ZgcPw?81^s-nM%{fSjtIapU zb`!#zzliq&TjZ*#QG00*wQfIkhr5-}!up5cr{0Fw|HwDD$>25O&TNj&Zaw$>rhD|? zZh2rD6F)OPFVAirKJwLgVZ8Wgl2lJTsGNLQIe))$e#!$Ox@p!mAKbw1D;|*yYoft_oRe-WEeELX6qyI zKOTajja+4S3`j_;V>Wl=<%{Oh@ba+zfb8PoB!1k;W5d@uFx1oVx=kKUlk%|#rP+t2 zbN5T<9+WO@+85+;=&F_F5h&JAUCOil&70X5!Oh`kz^z*^(Ba3yEqN3NPahnjrJ34o zAtH$aT`!?YH|RAHbFKgNrw@FULmRx#uA1@>f$#N)KnI8)1D8v+?)q787Gkt-3(I;C*x9S z8Pcl)e-L_bi^I!Y5 zPfOMJ>cH7;_g|&M?-w7GPTY0}Wpe!F*6|Ztr+)Bd>6GDY6<;1$P<~Q+c2IzF;E*HF k4aU%NN%h!Z9IYamJUPJ8Fx#2jodE06n}%!r*wDNFKfdBwO8@`> literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_backends/__init__.py b/.venv/Lib/site-packages/anyio/_backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5d55b4f7d7b7dd2d21f6632eb7f20f28aff7f87 GIT binary patch literal 212 zcmX@j%ge<81S^xRGC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!Dt5Mt2`x@7Dvk+G z%#CqLEzT~paZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%IiB<59S=Ih5Nft2Q@6zj*wXXa&=#K-FuRNmsS e$<0qG%}KQ@VgAwMS3!7u#S!f}_l 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_backends/_asyncio.py b/.venv/Lib/site-packages/anyio/_backends/_asyncio.py new file mode 100644 index 0000000..ed91f40 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_backends/_asyncio.py @@ -0,0 +1,2816 @@ +from __future__ import annotations + +import array +import asyncio +import concurrent.futures +import contextvars +import math +import os +import socket +import sys +import threading +import weakref +from asyncio import ( + AbstractEventLoop, + CancelledError, + all_tasks, + create_task, + current_task, + get_running_loop, + sleep, +) +from asyncio.base_events import _run_until_complete_cb # type: ignore[attr-defined] +from collections import OrderedDict, deque +from collections.abc import ( + AsyncGenerator, + AsyncIterator, + Awaitable, + Callable, + Collection, + Coroutine, + Iterable, + Sequence, +) +from concurrent.futures import Future +from contextlib import AbstractContextManager, suppress +from contextvars import Context, copy_context +from dataclasses import dataclass +from functools import partial, wraps +from inspect import ( + CORO_RUNNING, + CORO_SUSPENDED, + getcoroutinestate, + iscoroutine, +) +from io import IOBase +from os import PathLike +from queue import Queue +from signal import Signals +from socket import AddressFamily, SocketKind +from threading import Thread +from types import CodeType, TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Optional, + TypeVar, + cast, +) +from weakref import WeakKeyDictionary + +import sniffio + +from .. import ( + CapacityLimiterStatistics, + EventStatistics, + LockStatistics, + TaskInfo, + abc, +) +from .._core._eventloop import claim_worker_thread, threadlocals +from .._core._exceptions import ( + BrokenResourceError, + BusyResourceError, + ClosedResourceError, + EndOfStream, + WouldBlock, + iterate_exceptions, +) +from .._core._sockets import convert_ipv6_sockaddr +from .._core._streams import create_memory_object_stream +from .._core._synchronization import ( + CapacityLimiter as BaseCapacityLimiter, +) +from .._core._synchronization import Event as BaseEvent +from .._core._synchronization import Lock as BaseLock +from .._core._synchronization import ( + ResourceGuard, + SemaphoreStatistics, +) +from .._core._synchronization import Semaphore as BaseSemaphore +from .._core._tasks import CancelScope as BaseCancelScope +from ..abc import ( + AsyncBackend, + IPSockAddrType, + SocketListener, + UDPPacketType, + UNIXDatagramPacketType, +) +from ..abc._eventloop import StrOrBytesPath +from ..lowlevel import RunVar +from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike +else: + FileDescriptorLike = object + +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + +if sys.version_info >= (3, 11): + from asyncio import Runner + from typing import TypeVarTuple, Unpack +else: + import contextvars + import enum + import signal + from asyncio import coroutines, events, exceptions, tasks + + from exceptiongroup import BaseExceptionGroup + from typing_extensions import TypeVarTuple, Unpack + + class _State(enum.Enum): + CREATED = "created" + INITIALIZED = "initialized" + CLOSED = "closed" + + class Runner: + # Copied from CPython 3.11 + def __init__( + self, + *, + debug: bool | None = None, + loop_factory: Callable[[], AbstractEventLoop] | None = None, + ): + self._state = _State.CREATED + self._debug = debug + self._loop_factory = loop_factory + self._loop: AbstractEventLoop | None = None + self._context = None + self._interrupt_count = 0 + self._set_event_loop = False + + def __enter__(self) -> Runner: + self._lazy_init() + return self + + def __exit__( + self, + exc_type: type[BaseException], + exc_val: BaseException, + exc_tb: TracebackType, + ) -> None: + self.close() + + def close(self) -> None: + """Shutdown and close event loop.""" + if self._state is not _State.INITIALIZED: + return + try: + loop = self._loop + _cancel_all_tasks(loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + if hasattr(loop, "shutdown_default_executor"): + loop.run_until_complete(loop.shutdown_default_executor()) + else: + loop.run_until_complete(_shutdown_default_executor(loop)) + finally: + if self._set_event_loop: + events.set_event_loop(None) + loop.close() + self._loop = None + self._state = _State.CLOSED + + def get_loop(self) -> AbstractEventLoop: + """Return embedded event loop.""" + self._lazy_init() + return self._loop + + def run(self, coro: Coroutine[T_Retval], *, context=None) -> T_Retval: + """Run a coroutine inside the embedded event loop.""" + if not coroutines.iscoroutine(coro): + raise ValueError(f"a coroutine was expected, got {coro!r}") + + if events._get_running_loop() is not None: + # fail fast with short traceback + raise RuntimeError( + "Runner.run() cannot be called from a running event loop" + ) + + self._lazy_init() + + if context is None: + context = self._context + task = context.run(self._loop.create_task, coro) + + if ( + threading.current_thread() is threading.main_thread() + and signal.getsignal(signal.SIGINT) is signal.default_int_handler + ): + sigint_handler = partial(self._on_sigint, main_task=task) + try: + signal.signal(signal.SIGINT, sigint_handler) + except ValueError: + # `signal.signal` may throw if `threading.main_thread` does + # not support signals (e.g. embedded interpreter with signals + # not registered - see gh-91880) + sigint_handler = None + else: + sigint_handler = None + + self._interrupt_count = 0 + try: + return self._loop.run_until_complete(task) + except exceptions.CancelledError: + if self._interrupt_count > 0: + uncancel = getattr(task, "uncancel", None) + if uncancel is not None and uncancel() == 0: + raise KeyboardInterrupt() + raise # CancelledError + finally: + if ( + sigint_handler is not None + and signal.getsignal(signal.SIGINT) is sigint_handler + ): + signal.signal(signal.SIGINT, signal.default_int_handler) + + def _lazy_init(self) -> None: + if self._state is _State.CLOSED: + raise RuntimeError("Runner is closed") + if self._state is _State.INITIALIZED: + return + if self._loop_factory is None: + self._loop = events.new_event_loop() + if not self._set_event_loop: + # Call set_event_loop only once to avoid calling + # attach_loop multiple times on child watchers + events.set_event_loop(self._loop) + self._set_event_loop = True + else: + self._loop = self._loop_factory() + if self._debug is not None: + self._loop.set_debug(self._debug) + self._context = contextvars.copy_context() + self._state = _State.INITIALIZED + + def _on_sigint(self, signum, frame, main_task: asyncio.Task) -> None: + self._interrupt_count += 1 + if self._interrupt_count == 1 and not main_task.done(): + main_task.cancel() + # wakeup loop if it is blocked by select() with long timeout + self._loop.call_soon_threadsafe(lambda: None) + return + raise KeyboardInterrupt() + + def _cancel_all_tasks(loop: AbstractEventLoop) -> None: + to_cancel = tasks.all_tasks(loop) + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) + + async def _shutdown_default_executor(loop: AbstractEventLoop) -> None: + """Schedule the shutdown of the default executor.""" + + def _do_shutdown(future: asyncio.futures.Future) -> None: + try: + loop._default_executor.shutdown(wait=True) # type: ignore[attr-defined] + loop.call_soon_threadsafe(future.set_result, None) + except Exception as ex: + loop.call_soon_threadsafe(future.set_exception, ex) + + loop._executor_shutdown_called = True + if loop._default_executor is None: + return + future = loop.create_future() + thread = threading.Thread(target=_do_shutdown, args=(future,)) + thread.start() + try: + await future + finally: + thread.join() + + +T_Retval = TypeVar("T_Retval") +T_contra = TypeVar("T_contra", contravariant=True) +PosArgsT = TypeVarTuple("PosArgsT") +P = ParamSpec("P") + +_root_task: RunVar[asyncio.Task | None] = RunVar("_root_task") + + +def find_root_task() -> asyncio.Task: + root_task = _root_task.get(None) + if root_task is not None and not root_task.done(): + return root_task + + # Look for a task that has been started via run_until_complete() + for task in all_tasks(): + if task._callbacks and not task.done(): + callbacks = [cb for cb, context in task._callbacks] + for cb in callbacks: + if ( + cb is _run_until_complete_cb + or getattr(cb, "__module__", None) == "uvloop.loop" + ): + _root_task.set(task) + return task + + # Look up the topmost task in the AnyIO task tree, if possible + task = cast(asyncio.Task, current_task()) + state = _task_states.get(task) + if state: + cancel_scope = state.cancel_scope + while cancel_scope and cancel_scope._parent_scope is not None: + cancel_scope = cancel_scope._parent_scope + + if cancel_scope is not None: + return cast(asyncio.Task, cancel_scope._host_task) + + return task + + +def get_callable_name(func: Callable) -> str: + module = getattr(func, "__module__", None) + qualname = getattr(func, "__qualname__", None) + return ".".join([x for x in (module, qualname) if x]) + + +# +# Event loop +# + +_run_vars: WeakKeyDictionary[asyncio.AbstractEventLoop, Any] = WeakKeyDictionary() + + +def _task_started(task: asyncio.Task) -> bool: + """Return ``True`` if the task has been started and has not finished.""" + # The task coro should never be None here, as we never add finished tasks to the + # task list + coro = task.get_coro() + assert coro is not None + try: + return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED) + except AttributeError: + # task coro is async_genenerator_asend https://bugs.python.org/issue37771 + raise Exception(f"Cannot determine if task {task} has started or not") from None + + +# +# Timeouts and cancellation +# + + +def is_anyio_cancellation(exc: CancelledError) -> bool: + # Sometimes third party frameworks catch a CancelledError and raise a new one, so as + # a workaround we have to look at the previous ones in __context__ too for a + # matching cancel message + while True: + if ( + exc.args + and isinstance(exc.args[0], str) + and exc.args[0].startswith("Cancelled by cancel scope ") + ): + return True + + if isinstance(exc.__context__, CancelledError): + exc = exc.__context__ + continue + + return False + + +class CancelScope(BaseCancelScope): + def __new__( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return object.__new__(cls) + + def __init__(self, deadline: float = math.inf, shield: bool = False): + self._deadline = deadline + self._shield = shield + self._parent_scope: CancelScope | None = None + self._child_scopes: set[CancelScope] = set() + self._cancel_called = False + self._cancelled_caught = False + self._active = False + self._timeout_handle: asyncio.TimerHandle | None = None + self._cancel_handle: asyncio.Handle | None = None + self._tasks: set[asyncio.Task] = set() + self._host_task: asyncio.Task | None = None + if sys.version_info >= (3, 11): + self._pending_uncancellations: int | None = 0 + else: + self._pending_uncancellations = None + + def __enter__(self) -> CancelScope: + if self._active: + raise RuntimeError( + "Each CancelScope may only be used for a single 'with' block" + ) + + self._host_task = host_task = cast(asyncio.Task, current_task()) + self._tasks.add(host_task) + try: + task_state = _task_states[host_task] + except KeyError: + task_state = TaskState(None, self) + _task_states[host_task] = task_state + else: + self._parent_scope = task_state.cancel_scope + task_state.cancel_scope = self + if self._parent_scope is not None: + # If using an eager task factory, the parent scope may not even contain + # the host task + self._parent_scope._child_scopes.add(self) + self._parent_scope._tasks.discard(host_task) + + self._timeout() + self._active = True + + # Start cancelling the host task if the scope was cancelled before entering + if self._cancel_called: + self._deliver_cancellation(self) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + del exc_tb + + if not self._active: + raise RuntimeError("This cancel scope is not active") + if current_task() is not self._host_task: + raise RuntimeError( + "Attempted to exit cancel scope in a different task than it was " + "entered in" + ) + + assert self._host_task is not None + host_task_state = _task_states.get(self._host_task) + if host_task_state is None or host_task_state.cancel_scope is not self: + raise RuntimeError( + "Attempted to exit a cancel scope that isn't the current tasks's " + "current cancel scope" + ) + + try: + self._active = False + if self._timeout_handle: + self._timeout_handle.cancel() + self._timeout_handle = None + + self._tasks.remove(self._host_task) + if self._parent_scope is not None: + self._parent_scope._child_scopes.remove(self) + self._parent_scope._tasks.add(self._host_task) + + host_task_state.cancel_scope = self._parent_scope + + # Restart the cancellation effort in the closest visible, cancelled parent + # scope if necessary + self._restart_cancellation_in_parent() + + # We only swallow the exception iff it was an AnyIO CancelledError, either + # directly as exc_val or inside an exception group and there are no cancelled + # parent cancel scopes visible to us here + if self._cancel_called and not self._parent_cancellation_is_visible_to_us: + # For each level-cancel() call made on the host task, call uncancel() + while self._pending_uncancellations: + self._host_task.uncancel() + self._pending_uncancellations -= 1 + + # Update cancelled_caught and check for exceptions we must not swallow + cannot_swallow_exc_val = False + if exc_val is not None: + for exc in iterate_exceptions(exc_val): + if isinstance(exc, CancelledError) and is_anyio_cancellation( + exc + ): + self._cancelled_caught = True + else: + cannot_swallow_exc_val = True + + return self._cancelled_caught and not cannot_swallow_exc_val + else: + if self._pending_uncancellations: + assert self._parent_scope is not None + assert self._parent_scope._pending_uncancellations is not None + self._parent_scope._pending_uncancellations += ( + self._pending_uncancellations + ) + self._pending_uncancellations = 0 + + return False + finally: + self._host_task = None + del exc_val + + @property + def _effectively_cancelled(self) -> bool: + cancel_scope: CancelScope | None = self + while cancel_scope is not None: + if cancel_scope._cancel_called: + return True + + if cancel_scope.shield: + return False + + cancel_scope = cancel_scope._parent_scope + + return False + + @property + def _parent_cancellation_is_visible_to_us(self) -> bool: + return ( + self._parent_scope is not None + and not self.shield + and self._parent_scope._effectively_cancelled + ) + + def _timeout(self) -> None: + if self._deadline != math.inf: + loop = get_running_loop() + if loop.time() >= self._deadline: + self.cancel() + else: + self._timeout_handle = loop.call_at(self._deadline, self._timeout) + + def _deliver_cancellation(self, origin: CancelScope) -> bool: + """ + Deliver cancellation to directly contained tasks and nested cancel scopes. + + Schedule another run at the end if we still have tasks eligible for + cancellation. + + :param origin: the cancel scope that originated the cancellation + :return: ``True`` if the delivery needs to be retried on the next cycle + + """ + should_retry = False + current = current_task() + for task in self._tasks: + should_retry = True + if task._must_cancel: # type: ignore[attr-defined] + continue + + # The task is eligible for cancellation if it has started + if task is not current and (task is self._host_task or _task_started(task)): + waiter = task._fut_waiter # type: ignore[attr-defined] + if not isinstance(waiter, asyncio.Future) or not waiter.done(): + task.cancel(f"Cancelled by cancel scope {id(origin):x}") + if ( + task is origin._host_task + and origin._pending_uncancellations is not None + ): + origin._pending_uncancellations += 1 + + # Deliver cancellation to child scopes that aren't shielded or running their own + # cancellation callbacks + for scope in self._child_scopes: + if not scope._shield and not scope.cancel_called: + should_retry = scope._deliver_cancellation(origin) or should_retry + + # Schedule another callback if there are still tasks left + if origin is self: + if should_retry: + self._cancel_handle = get_running_loop().call_soon( + self._deliver_cancellation, origin + ) + else: + self._cancel_handle = None + + return should_retry + + def _restart_cancellation_in_parent(self) -> None: + """ + Restart the cancellation effort in the closest directly cancelled parent scope. + + """ + scope = self._parent_scope + while scope is not None: + if scope._cancel_called: + if scope._cancel_handle is None: + scope._deliver_cancellation(scope) + + break + + # No point in looking beyond any shielded scope + if scope._shield: + break + + scope = scope._parent_scope + + def cancel(self) -> None: + if not self._cancel_called: + if self._timeout_handle: + self._timeout_handle.cancel() + self._timeout_handle = None + + self._cancel_called = True + if self._host_task is not None: + self._deliver_cancellation(self) + + @property + def deadline(self) -> float: + return self._deadline + + @deadline.setter + def deadline(self, value: float) -> None: + self._deadline = float(value) + if self._timeout_handle is not None: + self._timeout_handle.cancel() + self._timeout_handle = None + + if self._active and not self._cancel_called: + self._timeout() + + @property + def cancel_called(self) -> bool: + return self._cancel_called + + @property + def cancelled_caught(self) -> bool: + return self._cancelled_caught + + @property + def shield(self) -> bool: + return self._shield + + @shield.setter + def shield(self, value: bool) -> None: + if self._shield != value: + self._shield = value + if not value: + self._restart_cancellation_in_parent() + + +# +# Task states +# + + +class TaskState: + """ + Encapsulates auxiliary task information that cannot be added to the Task instance + itself because there are no guarantees about its implementation. + """ + + __slots__ = "parent_id", "cancel_scope", "__weakref__" + + def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None): + self.parent_id = parent_id + self.cancel_scope = cancel_scope + + +_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary() + + +# +# Task groups +# + + +class _AsyncioTaskStatus(abc.TaskStatus): + def __init__(self, future: asyncio.Future, parent_id: int): + self._future = future + self._parent_id = parent_id + + def started(self, value: T_contra | None = None) -> None: + try: + self._future.set_result(value) + except asyncio.InvalidStateError: + if not self._future.cancelled(): + raise RuntimeError( + "called 'started' twice on the same task status" + ) from None + + task = cast(asyncio.Task, current_task()) + _task_states[task].parent_id = self._parent_id + + +if sys.version_info >= (3, 12): + _eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__ +else: + _eager_task_factory_code = None + + +class TaskGroup(abc.TaskGroup): + def __init__(self) -> None: + self.cancel_scope: CancelScope = CancelScope() + self._active = False + self._exceptions: list[BaseException] = [] + self._tasks: set[asyncio.Task] = set() + self._on_completed_fut: asyncio.Future[None] | None = None + + async def __aenter__(self) -> TaskGroup: + self.cancel_scope.__enter__() + self._active = True + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + try: + if exc_val is not None: + self.cancel_scope.cancel() + if not isinstance(exc_val, CancelledError): + self._exceptions.append(exc_val) + + loop = get_running_loop() + try: + if self._tasks: + with CancelScope() as wait_scope: + while self._tasks: + self._on_completed_fut = loop.create_future() + + try: + await self._on_completed_fut + except CancelledError as exc: + # Shield the scope against further cancellation attempts, + # as they're not productive (#695) + wait_scope.shield = True + self.cancel_scope.cancel() + + # Set exc_val from the cancellation exception if it was + # previously unset. However, we should not replace a native + # cancellation exception with one raise by a cancel scope. + if exc_val is None or ( + isinstance(exc_val, CancelledError) + and not is_anyio_cancellation(exc) + ): + exc_val = exc + + self._on_completed_fut = None + else: + # If there are no child tasks to wait on, run at least one checkpoint + # anyway + await AsyncIOBackend.cancel_shielded_checkpoint() + + self._active = False + if self._exceptions: + # The exception that got us here should already have been + # added to self._exceptions so it's ok to break exception + # chaining and avoid adding a "During handling of above..." + # for each nesting level. + raise BaseExceptionGroup( + "unhandled errors in a TaskGroup", self._exceptions + ) from None + elif exc_val: + raise exc_val + except BaseException as exc: + if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__): + return True + + raise + + return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb) + finally: + del exc_val, exc_tb, self._exceptions + + def _spawn( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[Any]], + args: tuple[Unpack[PosArgsT]], + name: object, + task_status_future: asyncio.Future | None = None, + ) -> asyncio.Task: + def task_done(_task: asyncio.Task) -> None: + task_state = _task_states[_task] + assert task_state.cancel_scope is not None + assert _task in task_state.cancel_scope._tasks + task_state.cancel_scope._tasks.remove(_task) + self._tasks.remove(task) + del _task_states[_task] + + if self._on_completed_fut is not None and not self._tasks: + try: + self._on_completed_fut.set_result(None) + except asyncio.InvalidStateError: + pass + + try: + exc = _task.exception() + except CancelledError as e: + while isinstance(e.__context__, CancelledError): + e = e.__context__ + + exc = e + + if exc is not None: + # The future can only be in the cancelled state if the host task was + # cancelled, so return immediately instead of adding one more + # CancelledError to the exceptions list + if task_status_future is not None and task_status_future.cancelled(): + return + + if task_status_future is None or task_status_future.done(): + if not isinstance(exc, CancelledError): + self._exceptions.append(exc) + + if not self.cancel_scope._effectively_cancelled: + self.cancel_scope.cancel() + else: + task_status_future.set_exception(exc) + elif task_status_future is not None and not task_status_future.done(): + task_status_future.set_exception( + RuntimeError("Child exited without calling task_status.started()") + ) + + if not self._active: + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + kwargs = {} + if task_status_future: + parent_id = id(current_task()) + kwargs["task_status"] = _AsyncioTaskStatus( + task_status_future, id(self.cancel_scope._host_task) + ) + else: + parent_id = id(self.cancel_scope._host_task) + + coro = func(*args, **kwargs) + if not iscoroutine(coro): + prefix = f"{func.__module__}." if hasattr(func, "__module__") else "" + raise TypeError( + f"Expected {prefix}{func.__qualname__}() to return a coroutine, but " + f"the return value ({coro!r}) is not a coroutine object" + ) + + name = get_callable_name(func) if name is None else str(name) + loop = asyncio.get_running_loop() + if ( + (factory := loop.get_task_factory()) + and getattr(factory, "__code__", None) is _eager_task_factory_code + and (closure := getattr(factory, "__closure__", None)) + ): + custom_task_constructor = closure[0].cell_contents + task = custom_task_constructor(coro, loop=loop, name=name) + else: + task = create_task(coro, name=name) + + # Make the spawned task inherit the task group's cancel scope + _task_states[task] = TaskState( + parent_id=parent_id, cancel_scope=self.cancel_scope + ) + self.cancel_scope._tasks.add(task) + self._tasks.add(task) + task.add_done_callback(task_done) + return task + + def start_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[Any]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> None: + self._spawn(func, args, name) + + async def start( + self, func: Callable[..., Awaitable[Any]], *args: object, name: object = None + ) -> Any: + future: asyncio.Future = asyncio.Future() + task = self._spawn(func, args, name, future) + + # If the task raises an exception after sending a start value without a switch + # point between, the task group is cancelled and this method never proceeds to + # process the completed future. That's why we have to have a shielded cancel + # scope here. + try: + return await future + except CancelledError: + # Cancel the task and wait for it to exit before returning + task.cancel() + with CancelScope(shield=True), suppress(CancelledError): + await task + + raise + + +# +# Threads +# + +_Retval_Queue_Type = tuple[Optional[T_Retval], Optional[BaseException]] + + +class WorkerThread(Thread): + MAX_IDLE_TIME = 10 # seconds + + def __init__( + self, + root_task: asyncio.Task, + workers: set[WorkerThread], + idle_workers: deque[WorkerThread], + ): + super().__init__(name="AnyIO worker thread") + self.root_task = root_task + self.workers = workers + self.idle_workers = idle_workers + self.loop = root_task._loop + self.queue: Queue[ + tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None + ] = Queue(2) + self.idle_since = AsyncIOBackend.current_time() + self.stopping = False + + def _report_result( + self, future: asyncio.Future, result: Any, exc: BaseException | None + ) -> None: + self.idle_since = AsyncIOBackend.current_time() + if not self.stopping: + self.idle_workers.append(self) + + if not future.cancelled(): + if exc is not None: + if isinstance(exc, StopIteration): + new_exc = RuntimeError("coroutine raised StopIteration") + new_exc.__cause__ = exc + exc = new_exc + + future.set_exception(exc) + else: + future.set_result(result) + + def run(self) -> None: + with claim_worker_thread(AsyncIOBackend, self.loop): + while True: + item = self.queue.get() + if item is None: + # Shutdown command received + return + + context, func, args, future, cancel_scope = item + if not future.cancelled(): + result = None + exception: BaseException | None = None + threadlocals.current_cancel_scope = cancel_scope + try: + result = context.run(func, *args) + except BaseException as exc: + exception = exc + finally: + del threadlocals.current_cancel_scope + + if not self.loop.is_closed(): + self.loop.call_soon_threadsafe( + self._report_result, future, result, exception + ) + + del result, exception + + self.queue.task_done() + del item, context, func, args, future, cancel_scope + + def stop(self, f: asyncio.Task | None = None) -> None: + self.stopping = True + self.queue.put_nowait(None) + self.workers.discard(self) + try: + self.idle_workers.remove(self) + except ValueError: + pass + + +_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar( + "_threadpool_idle_workers" +) +_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers") + + +class BlockingPortal(abc.BlockingPortal): + def __new__(cls) -> BlockingPortal: + return object.__new__(cls) + + def __init__(self) -> None: + super().__init__() + self._loop = get_running_loop() + + def _spawn_task_from_thread( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + name: object, + future: Future[T_Retval], + ) -> None: + AsyncIOBackend.run_sync_from_thread( + partial(self._task_group.start_soon, name=name), + (self._call_func, func, args, kwargs, future), + self._loop, + ) + + +# +# Subprocesses +# + + +@dataclass(eq=False) +class StreamReaderWrapper(abc.ByteReceiveStream): + _stream: asyncio.StreamReader + + async def receive(self, max_bytes: int = 65536) -> bytes: + data = await self._stream.read(max_bytes) + if data: + return data + else: + raise EndOfStream + + async def aclose(self) -> None: + self._stream.set_exception(ClosedResourceError()) + await AsyncIOBackend.checkpoint() + + +@dataclass(eq=False) +class StreamWriterWrapper(abc.ByteSendStream): + _stream: asyncio.StreamWriter + + async def send(self, item: bytes) -> None: + self._stream.write(item) + await self._stream.drain() + + async def aclose(self) -> None: + self._stream.close() + await AsyncIOBackend.checkpoint() + + +@dataclass(eq=False) +class Process(abc.Process): + _process: asyncio.subprocess.Process + _stdin: StreamWriterWrapper | None + _stdout: StreamReaderWrapper | None + _stderr: StreamReaderWrapper | None + + async def aclose(self) -> None: + with CancelScope(shield=True) as scope: + if self._stdin: + await self._stdin.aclose() + if self._stdout: + await self._stdout.aclose() + if self._stderr: + await self._stderr.aclose() + + scope.shield = False + try: + await self.wait() + except BaseException: + scope.shield = True + self.kill() + await self.wait() + raise + + async def wait(self) -> int: + return await self._process.wait() + + def terminate(self) -> None: + self._process.terminate() + + def kill(self) -> None: + self._process.kill() + + def send_signal(self, signal: int) -> None: + self._process.send_signal(signal) + + @property + def pid(self) -> int: + return self._process.pid + + @property + def returncode(self) -> int | None: + return self._process.returncode + + @property + def stdin(self) -> abc.ByteSendStream | None: + return self._stdin + + @property + def stdout(self) -> abc.ByteReceiveStream | None: + return self._stdout + + @property + def stderr(self) -> abc.ByteReceiveStream | None: + return self._stderr + + +def _forcibly_shutdown_process_pool_on_exit( + workers: set[Process], _task: object +) -> None: + """ + Forcibly shuts down worker processes belonging to this event loop.""" + child_watcher: asyncio.AbstractChildWatcher | None = None + if sys.version_info < (3, 12): + try: + child_watcher = asyncio.get_event_loop_policy().get_child_watcher() + except NotImplementedError: + pass + + # Close as much as possible (w/o async/await) to avoid warnings + for process in workers: + if process.returncode is None: + continue + + process._stdin._stream._transport.close() # type: ignore[union-attr] + process._stdout._stream._transport.close() # type: ignore[union-attr] + process._stderr._stream._transport.close() # type: ignore[union-attr] + process.kill() + if child_watcher: + child_watcher.remove_child_handler(process.pid) + + +async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None: + """ + Shuts down worker processes belonging to this event loop. + + NOTE: this only works when the event loop was started using asyncio.run() or + anyio.run(). + + """ + process: abc.Process + try: + await sleep(math.inf) + except asyncio.CancelledError: + for process in workers: + if process.returncode is None: + process.kill() + + for process in workers: + await process.aclose() + + +# +# Sockets and networking +# + + +class StreamProtocol(asyncio.Protocol): + read_queue: deque[bytes] + read_event: asyncio.Event + write_event: asyncio.Event + exception: Exception | None = None + is_at_eof: bool = False + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.read_queue = deque() + self.read_event = asyncio.Event() + self.write_event = asyncio.Event() + self.write_event.set() + cast(asyncio.Transport, transport).set_write_buffer_limits(0) + + def connection_lost(self, exc: Exception | None) -> None: + if exc: + self.exception = BrokenResourceError() + self.exception.__cause__ = exc + + self.read_event.set() + self.write_event.set() + + def data_received(self, data: bytes) -> None: + # ProactorEventloop sometimes sends bytearray instead of bytes + self.read_queue.append(bytes(data)) + self.read_event.set() + + def eof_received(self) -> bool | None: + self.is_at_eof = True + self.read_event.set() + return True + + def pause_writing(self) -> None: + self.write_event = asyncio.Event() + + def resume_writing(self) -> None: + self.write_event.set() + + +class DatagramProtocol(asyncio.DatagramProtocol): + read_queue: deque[tuple[bytes, IPSockAddrType]] + read_event: asyncio.Event + write_event: asyncio.Event + exception: Exception | None = None + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.read_queue = deque(maxlen=100) # arbitrary value + self.read_event = asyncio.Event() + self.write_event = asyncio.Event() + self.write_event.set() + + def connection_lost(self, exc: Exception | None) -> None: + self.read_event.set() + self.write_event.set() + + def datagram_received(self, data: bytes, addr: IPSockAddrType) -> None: + addr = convert_ipv6_sockaddr(addr) + self.read_queue.append((data, addr)) + self.read_event.set() + + def error_received(self, exc: Exception) -> None: + self.exception = exc + + def pause_writing(self) -> None: + self.write_event.clear() + + def resume_writing(self) -> None: + self.write_event.set() + + +class SocketStream(abc.SocketStream): + def __init__(self, transport: asyncio.Transport, protocol: StreamProtocol): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def receive(self, max_bytes: int = 65536) -> bytes: + with self._receive_guard: + if ( + not self._protocol.read_event.is_set() + and not self._transport.is_closing() + and not self._protocol.is_at_eof + ): + self._transport.resume_reading() + await self._protocol.read_event.wait() + self._transport.pause_reading() + else: + await AsyncIOBackend.checkpoint() + + try: + chunk = self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + elif self._protocol.exception: + raise self._protocol.exception from None + else: + raise EndOfStream from None + + if len(chunk) > max_bytes: + # Split the oversized chunk + chunk, leftover = chunk[:max_bytes], chunk[max_bytes:] + self._protocol.read_queue.appendleft(leftover) + + # If the read queue is empty, clear the flag so that the next call will + # block until data is available + if not self._protocol.read_queue: + self._protocol.read_event.clear() + + return chunk + + async def send(self, item: bytes) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + + if self._closed: + raise ClosedResourceError + elif self._protocol.exception is not None: + raise self._protocol.exception + + try: + self._transport.write(item) + except RuntimeError as exc: + if self._transport.is_closing(): + raise BrokenResourceError from exc + else: + raise + + await self._protocol.write_event.wait() + + async def send_eof(self) -> None: + try: + self._transport.write_eof() + except OSError: + pass + + async def aclose(self) -> None: + if not self._transport.is_closing(): + self._closed = True + try: + self._transport.write_eof() + except OSError: + pass + + self._transport.close() + await sleep(0) + self._transport.abort() + + +class _RawSocketMixin: + _receive_future: asyncio.Future | None = None + _send_future: asyncio.Future | None = None + _closing = False + + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + def _wait_until_readable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: + def callback(f: object) -> None: + del self._receive_future + loop.remove_reader(self.__raw_socket) + + f = self._receive_future = asyncio.Future() + loop.add_reader(self.__raw_socket, f.set_result, None) + f.add_done_callback(callback) + return f + + def _wait_until_writable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: + def callback(f: object) -> None: + del self._send_future + loop.remove_writer(self.__raw_socket) + + f = self._send_future = asyncio.Future() + loop.add_writer(self.__raw_socket, f.set_result, None) + f.add_done_callback(callback) + return f + + async def aclose(self) -> None: + if not self._closing: + self._closing = True + if self.__raw_socket.fileno() != -1: + self.__raw_socket.close() + + if self._receive_future: + self._receive_future.set_result(None) + if self._send_future: + self._send_future.set_result(None) + + +class UNIXSocketStream(_RawSocketMixin, abc.UNIXSocketStream): + async def send_eof(self) -> None: + with self._send_guard: + self._raw_socket.shutdown(socket.SHUT_WR) + + async def receive(self, max_bytes: int = 65536) -> bytes: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recv(max_bytes) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + if not data: + raise EndOfStream + + return data + + async def send(self, item: bytes) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + view = memoryview(item) + while view: + try: + bytes_sent = self._raw_socket.send(view) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + view = view[bytes_sent:] + + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + if not isinstance(msglen, int) or msglen < 0: + raise ValueError("msglen must be a non-negative integer") + if not isinstance(maxfds, int) or maxfds < 1: + raise ValueError("maxfds must be a positive integer") + + loop = get_running_loop() + fds = array.array("i") + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + message, ancdata, flags, addr = self._raw_socket.recvmsg( + msglen, socket.CMSG_LEN(maxfds * fds.itemsize) + ) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + if not message and not ancdata: + raise EndOfStream + + break + + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: + raise RuntimeError( + f"Received unexpected ancillary data; message = {message!r}, " + f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" + ) + + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + + return message, list(fds) + + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + if not message: + raise ValueError("message must not be empty") + if not fds: + raise ValueError("fds must not be empty") + + loop = get_running_loop() + filenos: list[int] = [] + for fd in fds: + if isinstance(fd, int): + filenos.append(fd) + elif isinstance(fd, IOBase): + filenos.append(fd.fileno()) + + fdarray = array.array("i", filenos) + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + # The ignore can be removed after mypy picks up + # https://github.com/python/typeshed/pull/5545 + self._raw_socket.sendmsg( + [message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fdarray)] + ) + break + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + + +class TCPSocketListener(abc.SocketListener): + _accept_scope: CancelScope | None = None + _closed = False + + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._loop = cast(asyncio.BaseEventLoop, get_running_loop()) + self._accept_guard = ResourceGuard("accepting connections from") + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + async def accept(self) -> abc.SocketStream: + if self._closed: + raise ClosedResourceError + + with self._accept_guard: + await AsyncIOBackend.checkpoint() + with CancelScope() as self._accept_scope: + try: + client_sock, _addr = await self._loop.sock_accept(self._raw_socket) + except asyncio.CancelledError: + # Workaround for https://bugs.python.org/issue41317 + try: + self._loop.remove_reader(self._raw_socket) + except (ValueError, NotImplementedError): + pass + + if self._closed: + raise ClosedResourceError from None + + raise + finally: + self._accept_scope = None + + client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + transport, protocol = await self._loop.connect_accepted_socket( + StreamProtocol, client_sock + ) + return SocketStream(transport, protocol) + + async def aclose(self) -> None: + if self._closed: + return + + self._closed = True + if self._accept_scope: + # Workaround for https://bugs.python.org/issue41317 + try: + self._loop.remove_reader(self._raw_socket) + except (ValueError, NotImplementedError): + pass + + self._accept_scope.cancel() + await sleep(0) + + self._raw_socket.close() + + +class UNIXSocketListener(abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._loop = get_running_loop() + self._accept_guard = ResourceGuard("accepting connections from") + self._closed = False + + async def accept(self) -> abc.SocketStream: + await AsyncIOBackend.checkpoint() + with self._accept_guard: + while True: + try: + client_sock, _ = self.__raw_socket.accept() + client_sock.setblocking(False) + return UNIXSocketStream(client_sock) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + self._loop.add_reader(self.__raw_socket, f.set_result, None) + f.add_done_callback( + lambda _: self._loop.remove_reader(self.__raw_socket) + ) + await f + except OSError as exc: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + + async def aclose(self) -> None: + self._closed = True + self.__raw_socket.close() + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + +class UDPSocket(abc.UDPSocket): + def __init__( + self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol + ): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def aclose(self) -> None: + if not self._transport.is_closing(): + self._closed = True + self._transport.close() + + async def receive(self) -> tuple[bytes, IPSockAddrType]: + with self._receive_guard: + await AsyncIOBackend.checkpoint() + + # If the buffer is empty, ask for more data + if not self._protocol.read_queue and not self._transport.is_closing(): + self._protocol.read_event.clear() + await self._protocol.read_event.wait() + + try: + return self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from None + + async def send(self, item: UDPPacketType) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + await self._protocol.write_event.wait() + if self._closed: + raise ClosedResourceError + elif self._transport.is_closing(): + raise BrokenResourceError + else: + self._transport.sendto(*item) + + +class ConnectedUDPSocket(abc.ConnectedUDPSocket): + def __init__( + self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol + ): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def aclose(self) -> None: + if not self._transport.is_closing(): + self._closed = True + self._transport.close() + + async def receive(self) -> bytes: + with self._receive_guard: + await AsyncIOBackend.checkpoint() + + # If the buffer is empty, ask for more data + if not self._protocol.read_queue and not self._transport.is_closing(): + self._protocol.read_event.clear() + await self._protocol.read_event.wait() + + try: + packet = self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from None + + return packet[0] + + async def send(self, item: bytes) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + await self._protocol.write_event.wait() + if self._closed: + raise ClosedResourceError + elif self._transport.is_closing(): + raise BrokenResourceError + else: + self._transport.sendto(item) + + +class UNIXDatagramSocket(_RawSocketMixin, abc.UNIXDatagramSocket): + async def receive(self) -> UNIXDatagramPacketType: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recvfrom(65536) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return data + + async def send(self, item: UNIXDatagramPacketType) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + self._raw_socket.sendto(*item) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return + + +class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSocket): + async def receive(self) -> bytes: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recv(65536) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return data + + async def send(self, item: bytes) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + self._raw_socket.send(item) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return + + +_read_events: RunVar[dict[int, asyncio.Event]] = RunVar("read_events") +_write_events: RunVar[dict[int, asyncio.Event]] = RunVar("write_events") + + +# +# Synchronization +# + + +class Event(BaseEvent): + def __new__(cls) -> Event: + return object.__new__(cls) + + def __init__(self) -> None: + self._event = asyncio.Event() + + def set(self) -> None: + self._event.set() + + def is_set(self) -> bool: + return self._event.is_set() + + async def wait(self) -> None: + if self.is_set(): + await AsyncIOBackend.checkpoint() + else: + await self._event.wait() + + def statistics(self) -> EventStatistics: + return EventStatistics(len(self._event._waiters)) + + +class Lock(BaseLock): + def __new__(cls, *, fast_acquire: bool = False) -> Lock: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False) -> None: + self._fast_acquire = fast_acquire + self._owner_task: asyncio.Task | None = None + self._waiters: deque[tuple[asyncio.Task, asyncio.Future]] = deque() + + async def acquire(self) -> None: + task = cast(asyncio.Task, current_task()) + if self._owner_task is None and not self._waiters: + await AsyncIOBackend.checkpoint_if_cancelled() + self._owner_task = task + + # Unless on the "fast path", yield control of the event loop so that other + # tasks can run too + if not self._fast_acquire: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except CancelledError: + self.release() + raise + + return + + if self._owner_task == task: + raise RuntimeError("Attempted to acquire an already held Lock") + + fut: asyncio.Future[None] = asyncio.Future() + item = task, fut + self._waiters.append(item) + try: + await fut + except CancelledError: + self._waiters.remove(item) + if self._owner_task is task: + self.release() + + raise + + self._waiters.remove(item) + + def acquire_nowait(self) -> None: + task = cast(asyncio.Task, current_task()) + if self._owner_task is None and not self._waiters: + self._owner_task = task + return + + if self._owner_task is task: + raise RuntimeError("Attempted to acquire an already held Lock") + + raise WouldBlock + + def locked(self) -> bool: + return self._owner_task is not None + + def release(self) -> None: + if self._owner_task != current_task(): + raise RuntimeError("The current task is not holding this lock") + + for task, fut in self._waiters: + if not fut.cancelled(): + self._owner_task = task + fut.set_result(None) + return + + self._owner_task = None + + def statistics(self) -> LockStatistics: + task_info = AsyncIOTaskInfo(self._owner_task) if self._owner_task else None + return LockStatistics(self.locked(), task_info, len(self._waiters)) + + +class Semaphore(BaseSemaphore): + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ): + super().__init__(initial_value, max_value=max_value) + self._value = initial_value + self._max_value = max_value + self._fast_acquire = fast_acquire + self._waiters: deque[asyncio.Future[None]] = deque() + + async def acquire(self) -> None: + if self._value > 0 and not self._waiters: + await AsyncIOBackend.checkpoint_if_cancelled() + self._value -= 1 + + # Unless on the "fast path", yield control of the event loop so that other + # tasks can run too + if not self._fast_acquire: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except CancelledError: + self.release() + raise + + return + + fut: asyncio.Future[None] = asyncio.Future() + self._waiters.append(fut) + try: + await fut + except CancelledError: + try: + self._waiters.remove(fut) + except ValueError: + self.release() + + raise + + def acquire_nowait(self) -> None: + if self._value == 0: + raise WouldBlock + + self._value -= 1 + + def release(self) -> None: + if self._max_value is not None and self._value == self._max_value: + raise ValueError("semaphore released too many times") + + for fut in self._waiters: + if not fut.cancelled(): + fut.set_result(None) + self._waiters.remove(fut) + return + + self._value += 1 + + @property + def value(self) -> int: + return self._value + + @property + def max_value(self) -> int | None: + return self._max_value + + def statistics(self) -> SemaphoreStatistics: + return SemaphoreStatistics(len(self._waiters)) + + +class CapacityLimiter(BaseCapacityLimiter): + _total_tokens: float = 0 + + def __new__(cls, total_tokens: float) -> CapacityLimiter: + return object.__new__(cls) + + def __init__(self, total_tokens: float): + self._borrowers: set[Any] = set() + self._wait_queue: OrderedDict[Any, asyncio.Event] = OrderedDict() + self.total_tokens = total_tokens + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + @property + def total_tokens(self) -> float: + return self._total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + if not isinstance(value, int) and not math.isinf(value): + raise TypeError("total_tokens must be an int or math.inf") + if value < 1: + raise ValueError("total_tokens must be >= 1") + + waiters_to_notify = max(value - self._total_tokens, 0) + self._total_tokens = value + + # Notify waiting tasks that they have acquired the limiter + while self._wait_queue and waiters_to_notify: + event = self._wait_queue.popitem(last=False)[1] + event.set() + waiters_to_notify -= 1 + + @property + def borrowed_tokens(self) -> int: + return len(self._borrowers) + + @property + def available_tokens(self) -> float: + return self._total_tokens - len(self._borrowers) + + def acquire_nowait(self) -> None: + self.acquire_on_behalf_of_nowait(current_task()) + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + if borrower in self._borrowers: + raise RuntimeError( + "this borrower is already holding one of this CapacityLimiter's tokens" + ) + + if self._wait_queue or len(self._borrowers) >= self._total_tokens: + raise WouldBlock + + self._borrowers.add(borrower) + + async def acquire(self) -> None: + return await self.acquire_on_behalf_of(current_task()) + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await AsyncIOBackend.checkpoint_if_cancelled() + try: + self.acquire_on_behalf_of_nowait(borrower) + except WouldBlock: + event = asyncio.Event() + self._wait_queue[borrower] = event + try: + await event.wait() + except BaseException: + self._wait_queue.pop(borrower, None) + raise + + self._borrowers.add(borrower) + else: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except BaseException: + self.release() + raise + + def release(self) -> None: + self.release_on_behalf_of(current_task()) + + def release_on_behalf_of(self, borrower: object) -> None: + try: + self._borrowers.remove(borrower) + except KeyError: + raise RuntimeError( + "this borrower isn't holding any of this CapacityLimiter's tokens" + ) from None + + # Notify the next task in line if this limiter has free capacity now + if self._wait_queue and len(self._borrowers) < self._total_tokens: + event = self._wait_queue.popitem(last=False)[1] + event.set() + + def statistics(self) -> CapacityLimiterStatistics: + return CapacityLimiterStatistics( + self.borrowed_tokens, + self.total_tokens, + tuple(self._borrowers), + len(self._wait_queue), + ) + + +_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter") + + +# +# Operating system signals +# + + +class _SignalReceiver: + def __init__(self, signals: tuple[Signals, ...]): + self._signals = signals + self._loop = get_running_loop() + self._signal_queue: deque[Signals] = deque() + self._future: asyncio.Future = asyncio.Future() + self._handled_signals: set[Signals] = set() + + def _deliver(self, signum: Signals) -> None: + self._signal_queue.append(signum) + if not self._future.done(): + self._future.set_result(None) + + def __enter__(self) -> _SignalReceiver: + for sig in set(self._signals): + self._loop.add_signal_handler(sig, self._deliver, sig) + self._handled_signals.add(sig) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + for sig in self._handled_signals: + self._loop.remove_signal_handler(sig) + + def __aiter__(self) -> _SignalReceiver: + return self + + async def __anext__(self) -> Signals: + await AsyncIOBackend.checkpoint() + if not self._signal_queue: + self._future = asyncio.Future() + await self._future + + return self._signal_queue.popleft() + + +# +# Testing and debugging +# + + +class AsyncIOTaskInfo(TaskInfo): + def __init__(self, task: asyncio.Task): + task_state = _task_states.get(task) + if task_state is None: + parent_id = None + else: + parent_id = task_state.parent_id + + coro = task.get_coro() + assert coro is not None, "created TaskInfo from a completed Task" + super().__init__(id(task), parent_id, task.get_name(), coro) + self._task = weakref.ref(task) + + def has_pending_cancellation(self) -> bool: + if not (task := self._task()): + # If the task isn't around anymore, it won't have a pending cancellation + return False + + if task._must_cancel: # type: ignore[attr-defined] + return True + elif ( + isinstance(task._fut_waiter, asyncio.Future) # type: ignore[attr-defined] + and task._fut_waiter.cancelled() # type: ignore[attr-defined] + ): + return True + + if task_state := _task_states.get(task): + if cancel_scope := task_state.cancel_scope: + return cancel_scope._effectively_cancelled + + return False + + +class TestRunner(abc.TestRunner): + _send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]] + + def __init__( + self, + *, + debug: bool | None = None, + use_uvloop: bool = False, + loop_factory: Callable[[], AbstractEventLoop] | None = None, + ) -> None: + if use_uvloop and loop_factory is None: + import uvloop + + loop_factory = uvloop.new_event_loop + + self._runner = Runner(debug=debug, loop_factory=loop_factory) + self._exceptions: list[BaseException] = [] + self._runner_task: asyncio.Task | None = None + + def __enter__(self) -> TestRunner: + self._runner.__enter__() + self.get_loop().set_exception_handler(self._exception_handler) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._runner.__exit__(exc_type, exc_val, exc_tb) + + def get_loop(self) -> AbstractEventLoop: + return self._runner.get_loop() + + def _exception_handler( + self, loop: asyncio.AbstractEventLoop, context: dict[str, Any] + ) -> None: + if isinstance(context.get("exception"), Exception): + self._exceptions.append(context["exception"]) + else: + loop.default_exception_handler(context) + + def _raise_async_exceptions(self) -> None: + # Re-raise any exceptions raised in asynchronous callbacks + if self._exceptions: + exceptions, self._exceptions = self._exceptions, [] + if len(exceptions) == 1: + raise exceptions[0] + elif exceptions: + raise BaseExceptionGroup( + "Multiple exceptions occurred in asynchronous callbacks", exceptions + ) + + async def _run_tests_and_fixtures( + self, + receive_stream: MemoryObjectReceiveStream[ + tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]] + ], + ) -> None: + from _pytest.outcomes import OutcomeException + + with receive_stream, self._send_stream: + async for coro, future in receive_stream: + try: + retval = await coro + except CancelledError as exc: + if not future.cancelled(): + future.cancel(*exc.args) + + raise + except BaseException as exc: + if not future.cancelled(): + future.set_exception(exc) + + if not isinstance(exc, (Exception, OutcomeException)): + raise + else: + if not future.cancelled(): + future.set_result(retval) + + async def _call_in_runner_task( + self, + func: Callable[P, Awaitable[T_Retval]], + *args: P.args, + **kwargs: P.kwargs, + ) -> T_Retval: + if not self._runner_task: + self._send_stream, receive_stream = create_memory_object_stream[ + tuple[Awaitable[Any], asyncio.Future] + ](1) + self._runner_task = self.get_loop().create_task( + self._run_tests_and_fixtures(receive_stream) + ) + + coro = func(*args, **kwargs) + future: asyncio.Future[T_Retval] = self.get_loop().create_future() + self._send_stream.send_nowait((coro, future)) + return await future + + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], + kwargs: dict[str, Any], + ) -> Iterable[T_Retval]: + asyncgen = fixture_func(**kwargs) + fixturevalue: T_Retval = self.get_loop().run_until_complete( + self._call_in_runner_task(asyncgen.asend, None) + ) + self._raise_async_exceptions() + + yield fixturevalue + + try: + self.get_loop().run_until_complete( + self._call_in_runner_task(asyncgen.asend, None) + ) + except StopAsyncIteration: + self._raise_async_exceptions() + else: + self.get_loop().run_until_complete(asyncgen.aclose()) + raise RuntimeError("Async generator fixture did not stop") + + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], + kwargs: dict[str, Any], + ) -> T_Retval: + retval = self.get_loop().run_until_complete( + self._call_in_runner_task(fixture_func, **kwargs) + ) + self._raise_async_exceptions() + return retval + + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + try: + self.get_loop().run_until_complete( + self._call_in_runner_task(test_func, **kwargs) + ) + except Exception as exc: + self._exceptions.append(exc) + + self._raise_async_exceptions() + + +class AsyncIOBackend(AsyncBackend): + @classmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + @wraps(func) + async def wrapper() -> T_Retval: + task = cast(asyncio.Task, current_task()) + task.set_name(get_callable_name(func)) + _task_states[task] = TaskState(None, None) + + try: + return await func(*args) + finally: + del _task_states[task] + + debug = options.get("debug", None) + loop_factory = options.get("loop_factory", None) + if loop_factory is None and options.get("use_uvloop", False): + import uvloop + + loop_factory = uvloop.new_event_loop + + with Runner(debug=debug, loop_factory=loop_factory) as runner: + return runner.run(wrapper()) + + @classmethod + def current_token(cls) -> object: + return get_running_loop() + + @classmethod + def current_time(cls) -> float: + return get_running_loop().time() + + @classmethod + def cancelled_exception_class(cls) -> type[BaseException]: + return CancelledError + + @classmethod + async def checkpoint(cls) -> None: + await sleep(0) + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + task = current_task() + if task is None: + return + + try: + cancel_scope = _task_states[task].cancel_scope + except KeyError: + return + + while cancel_scope: + if cancel_scope.cancel_called: + await sleep(0) + elif cancel_scope.shield: + break + else: + cancel_scope = cancel_scope._parent_scope + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + with CancelScope(shield=True): + await sleep(0) + + @classmethod + async def sleep(cls, delay: float) -> None: + await sleep(delay) + + @classmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return CancelScope(deadline=deadline, shield=shield) + + @classmethod + def current_effective_deadline(cls) -> float: + if (task := current_task()) is None: + return math.inf + + try: + cancel_scope = _task_states[task].cancel_scope + except KeyError: + return math.inf + + deadline = math.inf + while cancel_scope: + deadline = min(deadline, cancel_scope.deadline) + if cancel_scope._cancel_called: + deadline = -math.inf + break + elif cancel_scope.shield: + break + else: + cancel_scope = cancel_scope._parent_scope + + return deadline + + @classmethod + def create_task_group(cls) -> abc.TaskGroup: + return TaskGroup() + + @classmethod + def create_event(cls) -> abc.Event: + return Event() + + @classmethod + def create_lock(cls, *, fast_acquire: bool) -> abc.Lock: + return Lock(fast_acquire=fast_acquire) + + @classmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> abc.Semaphore: + return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) + + @classmethod + def create_capacity_limiter(cls, total_tokens: float) -> abc.CapacityLimiter: + return CapacityLimiter(total_tokens) + + @classmethod + async def run_sync_in_worker_thread( # type: ignore[return] + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: abc.CapacityLimiter | None = None, + ) -> T_Retval: + await cls.checkpoint() + + # If this is the first run in this event loop thread, set up the necessary + # variables + try: + idle_workers = _threadpool_idle_workers.get() + workers = _threadpool_workers.get() + except LookupError: + idle_workers = deque() + workers = set() + _threadpool_idle_workers.set(idle_workers) + _threadpool_workers.set(workers) + + async with limiter or cls.current_default_thread_limiter(): + with CancelScope(shield=not abandon_on_cancel) as scope: + future = asyncio.Future[T_Retval]() + root_task = find_root_task() + if not idle_workers: + worker = WorkerThread(root_task, workers, idle_workers) + worker.start() + workers.add(worker) + root_task.add_done_callback( + worker.stop, context=contextvars.Context() + ) + else: + worker = idle_workers.pop() + + # Prune any other workers that have been idle for MAX_IDLE_TIME + # seconds or longer + now = cls.current_time() + while idle_workers: + if ( + now - idle_workers[0].idle_since + < WorkerThread.MAX_IDLE_TIME + ): + break + + expired_worker = idle_workers.popleft() + expired_worker.root_task.remove_done_callback( + expired_worker.stop + ) + expired_worker.stop() + + context = copy_context() + context.run(sniffio.current_async_library_cvar.set, None) + if abandon_on_cancel or scope._parent_scope is None: + worker_scope = scope + else: + worker_scope = scope._parent_scope + + worker.queue.put_nowait((context, func, args, future, worker_scope)) + return await future + + @classmethod + def check_cancelled(cls) -> None: + scope: CancelScope | None = threadlocals.current_cancel_scope + while scope is not None: + if scope.cancel_called: + raise CancelledError(f"Cancelled by cancel scope {id(scope):x}") + + if scope.shield: + return + + scope = scope._parent_scope + + @classmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + async def task_wrapper(scope: CancelScope) -> T_Retval: + __tracebackhide__ = True + task = cast(asyncio.Task, current_task()) + _task_states[task] = TaskState(None, scope) + scope._tasks.add(task) + try: + return await func(*args) + except CancelledError as exc: + raise concurrent.futures.CancelledError(str(exc)) from None + finally: + scope._tasks.discard(task) + + loop = cast(AbstractEventLoop, token) + context = copy_context() + context.run(sniffio.current_async_library_cvar.set, "asyncio") + wrapper = task_wrapper(threadlocals.current_cancel_scope) + f: concurrent.futures.Future[T_Retval] = context.run( + asyncio.run_coroutine_threadsafe, wrapper, loop + ) + return f.result() + + @classmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + @wraps(func) + def wrapper() -> None: + try: + sniffio.current_async_library_cvar.set("asyncio") + f.set_result(func(*args)) + except BaseException as exc: + f.set_exception(exc) + if not isinstance(exc, Exception): + raise + + f: concurrent.futures.Future[T_Retval] = Future() + loop = cast(AbstractEventLoop, token) + loop.call_soon_threadsafe(wrapper) + return f.result() + + @classmethod + def create_blocking_portal(cls) -> abc.BlockingPortal: + return BlockingPortal() + + @classmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + await cls.checkpoint() + if isinstance(command, PathLike): + command = os.fspath(command) + + if isinstance(command, (str, bytes)): + process = await asyncio.create_subprocess_shell( + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + **kwargs, + ) + else: + process = await asyncio.create_subprocess_exec( + *command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + **kwargs, + ) + + stdin_stream = StreamWriterWrapper(process.stdin) if process.stdin else None + stdout_stream = StreamReaderWrapper(process.stdout) if process.stdout else None + stderr_stream = StreamReaderWrapper(process.stderr) if process.stderr else None + return Process(process, stdin_stream, stdout_stream, stderr_stream) + + @classmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: + create_task( + _shutdown_process_pool_on_exit(workers), + name="AnyIO process pool shutdown task", + ) + find_root_task().add_done_callback( + partial(_forcibly_shutdown_process_pool_on_exit, workers) # type:ignore[arg-type] + ) + + @classmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> abc.SocketStream: + transport, protocol = cast( + tuple[asyncio.Transport, StreamProtocol], + await get_running_loop().create_connection( + StreamProtocol, host, port, local_addr=local_address + ), + ) + transport.pause_reading() + return SocketStream(transport, protocol) + + @classmethod + async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: + await cls.checkpoint() + loop = get_running_loop() + raw_socket = socket.socket(socket.AF_UNIX) + raw_socket.setblocking(False) + while True: + try: + raw_socket.connect(path) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + loop.add_writer(raw_socket, f.set_result, None) + f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) + await f + except BaseException: + raw_socket.close() + raise + else: + return UNIXSocketStream(raw_socket) + + @classmethod + def create_tcp_listener(cls, sock: socket.socket) -> SocketListener: + return TCPSocketListener(sock) + + @classmethod + def create_unix_listener(cls, sock: socket.socket) -> SocketListener: + return UNIXSocketListener(sock) + + @classmethod + async def create_udp_socket( + cls, + family: AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, + local_addr=local_address, + remote_addr=remote_address, + family=family, + reuse_port=reuse_port, + ) + if protocol.exception: + transport.close() + raise protocol.exception + + if not remote_address: + return UDPSocket(transport, protocol) + else: + return ConnectedUDPSocket(transport, protocol) + + @classmethod + async def create_unix_datagram_socket( # type: ignore[override] + cls, raw_socket: socket.socket, remote_path: str | bytes | None + ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: + await cls.checkpoint() + loop = get_running_loop() + + if remote_path: + while True: + try: + raw_socket.connect(remote_path) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + loop.add_writer(raw_socket, f.set_result, None) + f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) + await f + except BaseException: + raw_socket.close() + raise + else: + return ConnectedUNIXDatagramSocket(raw_socket) + else: + return UNIXDatagramSocket(raw_socket) + + @classmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + return await get_running_loop().getaddrinfo( + host, port, family=family, type=type, proto=proto, flags=flags + ) + + @classmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + return await get_running_loop().getnameinfo(sockaddr, flags) + + @classmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + await cls.checkpoint() + try: + read_events = _read_events.get() + except LookupError: + read_events = {} + _read_events.set(read_events) + + if not isinstance(obj, int): + obj = obj.fileno() + + if read_events.get(obj): + raise BusyResourceError("reading from") + + loop = get_running_loop() + event = asyncio.Event() + try: + loop.add_reader(obj, event.set) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + selector = get_selector() + selector.add_reader(obj, event.set) + remove_reader = selector.remove_reader + else: + remove_reader = loop.remove_reader + + read_events[obj] = event + try: + await event.wait() + finally: + remove_reader(obj) + del read_events[obj] + + @classmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + await cls.checkpoint() + try: + write_events = _write_events.get() + except LookupError: + write_events = {} + _write_events.set(write_events) + + if not isinstance(obj, int): + obj = obj.fileno() + + if write_events.get(obj): + raise BusyResourceError("writing to") + + loop = get_running_loop() + event = asyncio.Event() + try: + loop.add_writer(obj, event.set) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + selector = get_selector() + selector.add_writer(obj, event.set) + remove_writer = selector.remove_writer + else: + remove_writer = loop.remove_writer + + write_events[obj] = event + try: + await event.wait() + finally: + del write_events[obj] + remove_writer(obj) + + @classmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + try: + return _default_thread_limiter.get() + except LookupError: + limiter = CapacityLimiter(40) + _default_thread_limiter.set(limiter) + return limiter + + @classmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + return _SignalReceiver(signals) + + @classmethod + def get_current_task(cls) -> TaskInfo: + return AsyncIOTaskInfo(current_task()) # type: ignore[arg-type] + + @classmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()] + + @classmethod + async def wait_all_tasks_blocked(cls) -> None: + await cls.checkpoint() + this_task = current_task() + while True: + for task in all_tasks(): + if task is this_task: + continue + + waiter = task._fut_waiter # type: ignore[attr-defined] + if waiter is None or waiter.done(): + await sleep(0.1) + break + else: + return + + @classmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + return TestRunner(**options) + + +backend_class = AsyncIOBackend diff --git a/.venv/Lib/site-packages/anyio/_backends/_trio.py b/.venv/Lib/site-packages/anyio/_backends/_trio.py new file mode 100644 index 0000000..b80cc04 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_backends/_trio.py @@ -0,0 +1,1334 @@ +from __future__ import annotations + +import array +import math +import os +import socket +import sys +import types +import weakref +from collections.abc import ( + AsyncGenerator, + AsyncIterator, + Awaitable, + Callable, + Collection, + Coroutine, + Iterable, + Sequence, +) +from concurrent.futures import Future +from contextlib import AbstractContextManager +from dataclasses import dataclass +from functools import partial +from io import IOBase +from os import PathLike +from signal import Signals +from socket import AddressFamily, SocketKind +from types import TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Generic, + NoReturn, + TypeVar, + cast, + overload, +) + +import trio.from_thread +import trio.lowlevel +from outcome import Error, Outcome, Value +from trio.lowlevel import ( + current_root_task, + current_task, + wait_readable, + wait_writable, +) +from trio.socket import SocketType as TrioSocketType +from trio.to_thread import run_sync + +from .. import ( + CapacityLimiterStatistics, + EventStatistics, + LockStatistics, + TaskInfo, + WouldBlock, + abc, +) +from .._core._eventloop import claim_worker_thread +from .._core._exceptions import ( + BrokenResourceError, + BusyResourceError, + ClosedResourceError, + EndOfStream, +) +from .._core._sockets import convert_ipv6_sockaddr +from .._core._streams import create_memory_object_stream +from .._core._synchronization import ( + CapacityLimiter as BaseCapacityLimiter, +) +from .._core._synchronization import Event as BaseEvent +from .._core._synchronization import Lock as BaseLock +from .._core._synchronization import ( + ResourceGuard, + SemaphoreStatistics, +) +from .._core._synchronization import Semaphore as BaseSemaphore +from .._core._tasks import CancelScope as BaseCancelScope +from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType +from ..abc._eventloop import AsyncBackend, StrOrBytesPath +from ..streams.memory import MemoryObjectSendStream + +if TYPE_CHECKING: + from _typeshed import HasFileno + +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from exceptiongroup import BaseExceptionGroup + from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +T_Retval = TypeVar("T_Retval") +T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType) +PosArgsT = TypeVarTuple("PosArgsT") +P = ParamSpec("P") + + +# +# Event loop +# + +RunVar = trio.lowlevel.RunVar + + +# +# Timeouts and cancellation +# + + +class CancelScope(BaseCancelScope): + def __new__( + cls, original: trio.CancelScope | None = None, **kwargs: object + ) -> CancelScope: + return object.__new__(cls) + + def __init__(self, original: trio.CancelScope | None = None, **kwargs: Any) -> None: + self.__original = original or trio.CancelScope(**kwargs) + + def __enter__(self) -> CancelScope: + self.__original.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + return self.__original.__exit__(exc_type, exc_val, exc_tb) + + def cancel(self) -> None: + self.__original.cancel() + + @property + def deadline(self) -> float: + return self.__original.deadline + + @deadline.setter + def deadline(self, value: float) -> None: + self.__original.deadline = value + + @property + def cancel_called(self) -> bool: + return self.__original.cancel_called + + @property + def cancelled_caught(self) -> bool: + return self.__original.cancelled_caught + + @property + def shield(self) -> bool: + return self.__original.shield + + @shield.setter + def shield(self, value: bool) -> None: + self.__original.shield = value + + +# +# Task groups +# + + +class TaskGroup(abc.TaskGroup): + def __init__(self) -> None: + self._active = False + self._nursery_manager = trio.open_nursery(strict_exception_groups=True) + self.cancel_scope = None # type: ignore[assignment] + + async def __aenter__(self) -> TaskGroup: + self._active = True + self._nursery = await self._nursery_manager.__aenter__() + self.cancel_scope = CancelScope(self._nursery.cancel_scope) + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + try: + # trio.Nursery.__exit__ returns bool; .open_nursery has wrong type + return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb) # type: ignore[return-value] + except BaseExceptionGroup as exc: + if not exc.split(trio.Cancelled)[1]: + raise trio.Cancelled._create() from exc + + raise + finally: + del exc_val, exc_tb + self._active = False + + def start_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[Any]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> None: + if not self._active: + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + self._nursery.start_soon(func, *args, name=name) + + async def start( + self, func: Callable[..., Awaitable[Any]], *args: object, name: object = None + ) -> Any: + if not self._active: + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + return await self._nursery.start(func, *args, name=name) + + +# +# Threads +# + + +class BlockingPortal(abc.BlockingPortal): + def __new__(cls) -> BlockingPortal: + return object.__new__(cls) + + def __init__(self) -> None: + super().__init__() + self._token = trio.lowlevel.current_trio_token() + + def _spawn_task_from_thread( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + name: object, + future: Future[T_Retval], + ) -> None: + trio.from_thread.run_sync( + partial(self._task_group.start_soon, name=name), + self._call_func, + func, + args, + kwargs, + future, + trio_token=self._token, + ) + + +# +# Subprocesses +# + + +@dataclass(eq=False) +class ReceiveStreamWrapper(abc.ByteReceiveStream): + _stream: trio.abc.ReceiveStream + + async def receive(self, max_bytes: int | None = None) -> bytes: + try: + data = await self._stream.receive_some(max_bytes) + except trio.ClosedResourceError as exc: + raise ClosedResourceError from exc.__cause__ + except trio.BrokenResourceError as exc: + raise BrokenResourceError from exc.__cause__ + + if data: + return data + else: + raise EndOfStream + + async def aclose(self) -> None: + await self._stream.aclose() + + +@dataclass(eq=False) +class SendStreamWrapper(abc.ByteSendStream): + _stream: trio.abc.SendStream + + async def send(self, item: bytes) -> None: + try: + await self._stream.send_all(item) + except trio.ClosedResourceError as exc: + raise ClosedResourceError from exc.__cause__ + except trio.BrokenResourceError as exc: + raise BrokenResourceError from exc.__cause__ + + async def aclose(self) -> None: + await self._stream.aclose() + + +@dataclass(eq=False) +class Process(abc.Process): + _process: trio.Process + _stdin: abc.ByteSendStream | None + _stdout: abc.ByteReceiveStream | None + _stderr: abc.ByteReceiveStream | None + + async def aclose(self) -> None: + with CancelScope(shield=True): + if self._stdin: + await self._stdin.aclose() + if self._stdout: + await self._stdout.aclose() + if self._stderr: + await self._stderr.aclose() + + try: + await self.wait() + except BaseException: + self.kill() + with CancelScope(shield=True): + await self.wait() + raise + + async def wait(self) -> int: + return await self._process.wait() + + def terminate(self) -> None: + self._process.terminate() + + def kill(self) -> None: + self._process.kill() + + def send_signal(self, signal: Signals) -> None: + self._process.send_signal(signal) + + @property + def pid(self) -> int: + return self._process.pid + + @property + def returncode(self) -> int | None: + return self._process.returncode + + @property + def stdin(self) -> abc.ByteSendStream | None: + return self._stdin + + @property + def stdout(self) -> abc.ByteReceiveStream | None: + return self._stdout + + @property + def stderr(self) -> abc.ByteReceiveStream | None: + return self._stderr + + +class _ProcessPoolShutdownInstrument(trio.abc.Instrument): + def after_run(self) -> None: + super().after_run() + + +current_default_worker_process_limiter: trio.lowlevel.RunVar = RunVar( + "current_default_worker_process_limiter" +) + + +async def _shutdown_process_pool(workers: set[abc.Process]) -> None: + try: + await trio.sleep(math.inf) + except trio.Cancelled: + for process in workers: + if process.returncode is None: + process.kill() + + with CancelScope(shield=True): + for process in workers: + await process.aclose() + + +# +# Sockets and networking +# + + +class _TrioSocketMixin(Generic[T_SockAddr]): + def __init__(self, trio_socket: TrioSocketType) -> None: + self._trio_socket = trio_socket + self._closed = False + + def _check_closed(self) -> None: + if self._closed: + raise ClosedResourceError + if self._trio_socket.fileno() < 0: + raise BrokenResourceError + + @property + def _raw_socket(self) -> socket.socket: + return self._trio_socket._sock # type: ignore[attr-defined] + + async def aclose(self) -> None: + if self._trio_socket.fileno() >= 0: + self._closed = True + self._trio_socket.close() + + def _convert_socket_error(self, exc: BaseException) -> NoReturn: + if isinstance(exc, trio.ClosedResourceError): + raise ClosedResourceError from exc + elif self._trio_socket.fileno() < 0 and self._closed: + raise ClosedResourceError from None + elif isinstance(exc, OSError): + raise BrokenResourceError from exc + else: + raise exc + + +class SocketStream(_TrioSocketMixin, abc.SocketStream): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self, max_bytes: int = 65536) -> bytes: + with self._receive_guard: + try: + data = await self._trio_socket.recv(max_bytes) + except BaseException as exc: + self._convert_socket_error(exc) + + if data: + return data + else: + raise EndOfStream + + async def send(self, item: bytes) -> None: + with self._send_guard: + view = memoryview(item) + while view: + try: + bytes_sent = await self._trio_socket.send(view) + except BaseException as exc: + self._convert_socket_error(exc) + + view = view[bytes_sent:] + + async def send_eof(self) -> None: + self._trio_socket.shutdown(socket.SHUT_WR) + + +class UNIXSocketStream(SocketStream, abc.UNIXSocketStream): + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + if not isinstance(msglen, int) or msglen < 0: + raise ValueError("msglen must be a non-negative integer") + if not isinstance(maxfds, int) or maxfds < 1: + raise ValueError("maxfds must be a positive integer") + + fds = array.array("i") + await trio.lowlevel.checkpoint() + with self._receive_guard: + while True: + try: + message, ancdata, flags, addr = await self._trio_socket.recvmsg( + msglen, socket.CMSG_LEN(maxfds * fds.itemsize) + ) + except BaseException as exc: + self._convert_socket_error(exc) + else: + if not message and not ancdata: + raise EndOfStream + + break + + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: + raise RuntimeError( + f"Received unexpected ancillary data; message = {message!r}, " + f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" + ) + + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + + return message, list(fds) + + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + if not message: + raise ValueError("message must not be empty") + if not fds: + raise ValueError("fds must not be empty") + + filenos: list[int] = [] + for fd in fds: + if isinstance(fd, int): + filenos.append(fd) + elif isinstance(fd, IOBase): + filenos.append(fd.fileno()) + + fdarray = array.array("i", filenos) + await trio.lowlevel.checkpoint() + with self._send_guard: + while True: + try: + await self._trio_socket.sendmsg( + [message], + [ + ( + socket.SOL_SOCKET, + socket.SCM_RIGHTS, + fdarray, + ) + ], + ) + break + except BaseException as exc: + self._convert_socket_error(exc) + + +class TCPSocketListener(_TrioSocketMixin, abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + super().__init__(trio.socket.from_stdlib_socket(raw_socket)) + self._accept_guard = ResourceGuard("accepting connections from") + + async def accept(self) -> SocketStream: + with self._accept_guard: + try: + trio_socket, _addr = await self._trio_socket.accept() + except BaseException as exc: + self._convert_socket_error(exc) + + trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + return SocketStream(trio_socket) + + +class UNIXSocketListener(_TrioSocketMixin, abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + super().__init__(trio.socket.from_stdlib_socket(raw_socket)) + self._accept_guard = ResourceGuard("accepting connections from") + + async def accept(self) -> UNIXSocketStream: + with self._accept_guard: + try: + trio_socket, _addr = await self._trio_socket.accept() + except BaseException as exc: + self._convert_socket_error(exc) + + return UNIXSocketStream(trio_socket) + + +class UDPSocket(_TrioSocketMixin[IPSockAddrType], abc.UDPSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> tuple[bytes, IPSockAddrType]: + with self._receive_guard: + try: + data, addr = await self._trio_socket.recvfrom(65536) + return data, convert_ipv6_sockaddr(addr) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: UDPPacketType) -> None: + with self._send_guard: + try: + await self._trio_socket.sendto(*item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class ConnectedUDPSocket(_TrioSocketMixin[IPSockAddrType], abc.ConnectedUDPSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> bytes: + with self._receive_guard: + try: + return await self._trio_socket.recv(65536) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: bytes) -> None: + with self._send_guard: + try: + await self._trio_socket.send(item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class UNIXDatagramSocket(_TrioSocketMixin[str], abc.UNIXDatagramSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> UNIXDatagramPacketType: + with self._receive_guard: + try: + data, addr = await self._trio_socket.recvfrom(65536) + return data, addr + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: UNIXDatagramPacketType) -> None: + with self._send_guard: + try: + await self._trio_socket.sendto(*item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class ConnectedUNIXDatagramSocket( + _TrioSocketMixin[str], abc.ConnectedUNIXDatagramSocket +): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> bytes: + with self._receive_guard: + try: + return await self._trio_socket.recv(65536) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: bytes) -> None: + with self._send_guard: + try: + await self._trio_socket.send(item) + except BaseException as exc: + self._convert_socket_error(exc) + + +# +# Synchronization +# + + +class Event(BaseEvent): + def __new__(cls) -> Event: + return object.__new__(cls) + + def __init__(self) -> None: + self.__original = trio.Event() + + def is_set(self) -> bool: + return self.__original.is_set() + + async def wait(self) -> None: + return await self.__original.wait() + + def statistics(self) -> EventStatistics: + orig_statistics = self.__original.statistics() + return EventStatistics(tasks_waiting=orig_statistics.tasks_waiting) + + def set(self) -> None: + self.__original.set() + + +class Lock(BaseLock): + def __new__(cls, *, fast_acquire: bool = False) -> Lock: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False) -> None: + self._fast_acquire = fast_acquire + self.__original = trio.Lock() + + @staticmethod + def _convert_runtime_error_msg(exc: RuntimeError) -> None: + if exc.args == ("attempt to re-acquire an already held Lock",): + exc.args = ("Attempted to acquire an already held Lock",) + + async def acquire(self) -> None: + if not self._fast_acquire: + try: + await self.__original.acquire() + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + return + + # This is the "fast path" where we don't let other tasks run + await trio.lowlevel.checkpoint_if_cancelled() + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + await self.__original._lot.park() + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + def locked(self) -> bool: + return self.__original.locked() + + def release(self) -> None: + self.__original.release() + + def statistics(self) -> LockStatistics: + orig_statistics = self.__original.statistics() + owner = TrioTaskInfo(orig_statistics.owner) if orig_statistics.owner else None + return LockStatistics( + orig_statistics.locked, owner, orig_statistics.tasks_waiting + ) + + +class Semaphore(BaseSemaphore): + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> None: + super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) + self.__original = trio.Semaphore(initial_value, max_value=max_value) + + async def acquire(self) -> None: + if not self._fast_acquire: + await self.__original.acquire() + return + + # This is the "fast path" where we don't let other tasks run + await trio.lowlevel.checkpoint_if_cancelled() + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + await self.__original._lot.park() + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + + @property + def max_value(self) -> int | None: + return self.__original.max_value + + @property + def value(self) -> int: + return self.__original.value + + def release(self) -> None: + self.__original.release() + + def statistics(self) -> SemaphoreStatistics: + orig_statistics = self.__original.statistics() + return SemaphoreStatistics(orig_statistics.tasks_waiting) + + +class CapacityLimiter(BaseCapacityLimiter): + def __new__( + cls, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, + ) -> CapacityLimiter: + return object.__new__(cls) + + def __init__( + self, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, + ) -> None: + if original is not None: + self.__original = original + else: + assert total_tokens is not None + self.__original = trio.CapacityLimiter(total_tokens) + + async def __aenter__(self) -> None: + return await self.__original.__aenter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.__original.__aexit__(exc_type, exc_val, exc_tb) + + @property + def total_tokens(self) -> float: + return self.__original.total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + self.__original.total_tokens = value + + @property + def borrowed_tokens(self) -> int: + return self.__original.borrowed_tokens + + @property + def available_tokens(self) -> float: + return self.__original.available_tokens + + def acquire_nowait(self) -> None: + self.__original.acquire_nowait() + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + self.__original.acquire_on_behalf_of_nowait(borrower) + + async def acquire(self) -> None: + await self.__original.acquire() + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await self.__original.acquire_on_behalf_of(borrower) + + def release(self) -> None: + return self.__original.release() + + def release_on_behalf_of(self, borrower: object) -> None: + return self.__original.release_on_behalf_of(borrower) + + def statistics(self) -> CapacityLimiterStatistics: + orig = self.__original.statistics() + return CapacityLimiterStatistics( + borrowed_tokens=orig.borrowed_tokens, + total_tokens=orig.total_tokens, + borrowers=tuple(orig.borrowers), + tasks_waiting=orig.tasks_waiting, + ) + + +_capacity_limiter_wrapper: trio.lowlevel.RunVar = RunVar("_capacity_limiter_wrapper") + + +# +# Signal handling +# + + +class _SignalReceiver: + _iterator: AsyncIterator[int] + + def __init__(self, signals: tuple[Signals, ...]): + self._signals = signals + + def __enter__(self) -> _SignalReceiver: + self._cm = trio.open_signal_receiver(*self._signals) + self._iterator = self._cm.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + return self._cm.__exit__(exc_type, exc_val, exc_tb) + + def __aiter__(self) -> _SignalReceiver: + return self + + async def __anext__(self) -> Signals: + signum = await self._iterator.__anext__() + return Signals(signum) + + +# +# Testing and debugging +# + + +class TestRunner(abc.TestRunner): + def __init__(self, **options: Any) -> None: + from queue import Queue + + self._call_queue: Queue[Callable[[], object]] = Queue() + self._send_stream: MemoryObjectSendStream | None = None + self._options = options + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + if self._send_stream: + self._send_stream.close() + while self._send_stream is not None: + self._call_queue.get()() + + async def _run_tests_and_fixtures(self) -> None: + self._send_stream, receive_stream = create_memory_object_stream(1) + with receive_stream: + async for coro, outcome_holder in receive_stream: + try: + retval = await coro + except BaseException as exc: + outcome_holder.append(Error(exc)) + else: + outcome_holder.append(Value(retval)) + + def _main_task_finished(self, outcome: object) -> None: + self._send_stream = None + + def _call_in_runner_task( + self, + func: Callable[P, Awaitable[T_Retval]], + *args: P.args, + **kwargs: P.kwargs, + ) -> T_Retval: + if self._send_stream is None: + trio.lowlevel.start_guest_run( + self._run_tests_and_fixtures, + run_sync_soon_threadsafe=self._call_queue.put, + done_callback=self._main_task_finished, + **self._options, + ) + while self._send_stream is None: + self._call_queue.get()() + + outcome_holder: list[Outcome] = [] + self._send_stream.send_nowait((func(*args, **kwargs), outcome_holder)) + while not outcome_holder: + self._call_queue.get()() + + return outcome_holder[0].unwrap() + + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], + kwargs: dict[str, Any], + ) -> Iterable[T_Retval]: + asyncgen = fixture_func(**kwargs) + fixturevalue: T_Retval = self._call_in_runner_task(asyncgen.asend, None) + + yield fixturevalue + + try: + self._call_in_runner_task(asyncgen.asend, None) + except StopAsyncIteration: + pass + else: + self._call_in_runner_task(asyncgen.aclose) + raise RuntimeError("Async generator fixture did not stop") + + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], + kwargs: dict[str, Any], + ) -> T_Retval: + return self._call_in_runner_task(fixture_func, **kwargs) + + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + self._call_in_runner_task(test_func, **kwargs) + + +class TrioTaskInfo(TaskInfo): + def __init__(self, task: trio.lowlevel.Task): + parent_id = None + if task.parent_nursery and task.parent_nursery.parent_task: + parent_id = id(task.parent_nursery.parent_task) + + super().__init__(id(task), parent_id, task.name, task.coro) + self._task = weakref.proxy(task) + + def has_pending_cancellation(self) -> bool: + try: + return self._task._cancel_status.effectively_cancelled + except ReferenceError: + # If the task is no longer around, it surely doesn't have a cancellation + # pending + return False + + +class TrioBackend(AsyncBackend): + @classmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + return trio.run(func, *args) + + @classmethod + def current_token(cls) -> object: + return trio.lowlevel.current_trio_token() + + @classmethod + def current_time(cls) -> float: + return trio.current_time() + + @classmethod + def cancelled_exception_class(cls) -> type[BaseException]: + return trio.Cancelled + + @classmethod + async def checkpoint(cls) -> None: + await trio.lowlevel.checkpoint() + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + await trio.lowlevel.checkpoint_if_cancelled() + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + await trio.lowlevel.cancel_shielded_checkpoint() + + @classmethod + async def sleep(cls, delay: float) -> None: + await trio.sleep(delay) + + @classmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> abc.CancelScope: + return CancelScope(deadline=deadline, shield=shield) + + @classmethod + def current_effective_deadline(cls) -> float: + return trio.current_effective_deadline() + + @classmethod + def create_task_group(cls) -> abc.TaskGroup: + return TaskGroup() + + @classmethod + def create_event(cls) -> abc.Event: + return Event() + + @classmethod + def create_lock(cls, *, fast_acquire: bool) -> Lock: + return Lock(fast_acquire=fast_acquire) + + @classmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> abc.Semaphore: + return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) + + @classmethod + def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: + return CapacityLimiter(total_tokens) + + @classmethod + async def run_sync_in_worker_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: abc.CapacityLimiter | None = None, + ) -> T_Retval: + def wrapper() -> T_Retval: + with claim_worker_thread(TrioBackend, token): + return func(*args) + + token = TrioBackend.current_token() + return await run_sync( + wrapper, + abandon_on_cancel=abandon_on_cancel, + limiter=cast(trio.CapacityLimiter, limiter), + ) + + @classmethod + def check_cancelled(cls) -> None: + trio.from_thread.check_cancelled() + + @classmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + return trio.from_thread.run(func, *args) + + @classmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + return trio.from_thread.run_sync(func, *args) + + @classmethod + def create_blocking_portal(cls) -> abc.BlockingPortal: + return BlockingPortal() + + @classmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + def convert_item(item: StrOrBytesPath) -> str: + str_or_bytes = os.fspath(item) + if isinstance(str_or_bytes, str): + return str_or_bytes + else: + return os.fsdecode(str_or_bytes) + + if isinstance(command, (str, bytes, PathLike)): + process = await trio.lowlevel.open_process( + convert_item(command), + stdin=stdin, + stdout=stdout, + stderr=stderr, + shell=True, + **kwargs, + ) + else: + process = await trio.lowlevel.open_process( + [convert_item(item) for item in command], + stdin=stdin, + stdout=stdout, + stderr=stderr, + shell=False, + **kwargs, + ) + + stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None + stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout else None + stderr_stream = ReceiveStreamWrapper(process.stderr) if process.stderr else None + return Process(process, stdin_stream, stdout_stream, stderr_stream) + + @classmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: + trio.lowlevel.spawn_system_task(_shutdown_process_pool, workers) + + @classmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> SocketStream: + family = socket.AF_INET6 if ":" in host else socket.AF_INET + trio_socket = trio.socket.socket(family) + trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if local_address: + await trio_socket.bind(local_address) + + try: + await trio_socket.connect((host, port)) + except BaseException: + trio_socket.close() + raise + + return SocketStream(trio_socket) + + @classmethod + async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: + trio_socket = trio.socket.socket(socket.AF_UNIX) + try: + await trio_socket.connect(path) + except BaseException: + trio_socket.close() + raise + + return UNIXSocketStream(trio_socket) + + @classmethod + def create_tcp_listener(cls, sock: socket.socket) -> abc.SocketListener: + return TCPSocketListener(sock) + + @classmethod + def create_unix_listener(cls, sock: socket.socket) -> abc.SocketListener: + return UNIXSocketListener(sock) + + @classmethod + async def create_udp_socket( + cls, + family: socket.AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + trio_socket = trio.socket.socket(family=family, type=socket.SOCK_DGRAM) + + if reuse_port: + trio_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + if local_address: + await trio_socket.bind(local_address) + + if remote_address: + await trio_socket.connect(remote_address) + return ConnectedUDPSocket(trio_socket) + else: + return UDPSocket(trio_socket) + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: None + ) -> abc.UNIXDatagramSocket: ... + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: str | bytes + ) -> abc.ConnectedUNIXDatagramSocket: ... + + @classmethod + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: str | bytes | None + ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: + trio_socket = trio.socket.from_stdlib_socket(raw_socket) + + if remote_path: + await trio_socket.connect(remote_path) + return ConnectedUNIXDatagramSocket(trio_socket) + else: + return UNIXDatagramSocket(trio_socket) + + @classmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + return await trio.socket.getaddrinfo(host, port, family, type, proto, flags) + + @classmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + return await trio.socket.getnameinfo(sockaddr, flags) + + @classmethod + async def wait_readable(cls, obj: HasFileno | int) -> None: + try: + await wait_readable(obj) + except trio.ClosedResourceError as exc: + raise ClosedResourceError().with_traceback(exc.__traceback__) from None + except trio.BusyResourceError: + raise BusyResourceError("reading from") from None + + @classmethod + async def wait_writable(cls, obj: HasFileno | int) -> None: + try: + await wait_writable(obj) + except trio.ClosedResourceError as exc: + raise ClosedResourceError().with_traceback(exc.__traceback__) from None + except trio.BusyResourceError: + raise BusyResourceError("writing to") from None + + @classmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + try: + return _capacity_limiter_wrapper.get() + except LookupError: + limiter = CapacityLimiter( + original=trio.to_thread.current_default_thread_limiter() + ) + _capacity_limiter_wrapper.set(limiter) + return limiter + + @classmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + return _SignalReceiver(signals) + + @classmethod + def get_current_task(cls) -> TaskInfo: + task = current_task() + return TrioTaskInfo(task) + + @classmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + root_task = current_root_task() + assert root_task + task_infos = [TrioTaskInfo(root_task)] + nurseries = root_task.child_nurseries + while nurseries: + new_nurseries: list[trio.Nursery] = [] + for nursery in nurseries: + for task in nursery.child_tasks: + task_infos.append(TrioTaskInfo(task)) + new_nurseries.extend(task.child_nurseries) + + nurseries = new_nurseries + + return task_infos + + @classmethod + async def wait_all_tasks_blocked(cls) -> None: + from trio.testing import wait_all_tasks_blocked + + await wait_all_tasks_blocked() + + @classmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + return TestRunner(**options) + + +backend_class = TrioBackend diff --git a/.venv/Lib/site-packages/anyio/_core/__init__.py b/.venv/Lib/site-packages/anyio/_core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3becde1e95d388dbdbe81bd0d5fa38265bd1e4e GIT binary patch literal 208 zcmX@j%ge<81S^xRGC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!%1^e62`x@7Dvk+G z%#CqLEzT~paZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%IiB<59S=EuY*=NF~M#K&jmWtPOp>lIYq;;_lh cPbtkwwJTx;+Rq5Y#UREpaZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%IiB<59S=Ih5N=NF~w$H!;pWtPOp>lIYq;;_lh cPbtkwwJTx;+Rq5Y#URE;JH6%x(Bf{^>up5?aIk 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 X{@B0gGe7hj4Q}BIN61xjoG3)$G9=3-I1byc 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 BKHdNT literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14451fd2ec9a93079a8afc8ad03fbca694158f9b GIT binary patch literal 6188 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 E7xHV~s{jB1 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f30c29776b31d68f7eb04789f71430691e3b56b9 GIT binary patch literal 6075 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a95412a58bd1650a39a463be8129902da962539 GIT binary patch literal 41632 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 HU8lx4& z7O_z2&3f`)ss|OkO3z*d5zB%gc+lR27A)eye>T|^|An1zzW@Et%!fk3Kp^XOrT!Kp z^j&RYN%R2Txej0rImp2tYT_Ek3MafoO^eoKGi4_*YU;H#Xf!A3Wt!Pq7I4bZJ)@bc z3}R$W!JZFG0EG)X4_CyWI959n|YHq zZ!ueOvFHF65yCq!0Ng+r3JnJOjlR`+^Z+m5h88>RudxM23#gIW=VKJ>7oqw4;Qvfp z(;lZ%kjKI5+lYsRy7&TGDV##*ki;Dr#}wFLVDD;EsDoc=9b8J>dub4m5synos86VB zwJx~N+meWW%ftl*v7>%H<`AiRE*p{OD0lr?64YHuY!yTTX8WEUH>mS=KswY7lp%*m zeizLB*?xdZiwfEtcvSxP4|p$ZFWGoW6KNPksP~Eqbs8ltCBjTxY?zA5lWGiv{JCRS z%-fO)X-?9nIm+Z*z+2{|>o77Qc!Sx2G{>k6suS19yd+~Dm2>7*YR@r2MtOsq<$31M zn>XEQQ@Q~=+X5AxWzvK;Ty9!67tFNcgTDXiv~^D9GJLx)%SGXFGG{E{ht%oKm8ka6V6@V88HuMhvq61YFg zRSWp(=_=M<bZ9(wll3h*DDIz+R|J6zYNnZQ+N>aqhEHAv7#& zrVR|UETL{;n3-<79-NU3^H*A3Yp^?JwYmY5L77Szqk0)-!8!tF5oQNen2tFEoRzsn zMX1qfyPw?(V6HMcyl5EfXa|xaQHWAsY;vdVu}k6vY}H$lN8$HN!x(=>Cx0Ncgg&F< OS5(~9bC`(Zp!^38^BnvD literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..908e780b8f285f05035ba130f4c1a04f7734b812 GIT binary patch literal 950 zcmY*XO=uHA6rRauH|eGcsI;C+27eBFu+@VIL6p!^6r~i4hlYh^x;t$)-R#7fO=Glz zSP=`A-mE9@rFu}otMu$e@L*Xe2qM^<(1Ic!e6z{6_!efrdGGt)AM>$TG!fXQTdBXp z2z^tR)G|GQ_s;{^KpygNfSS05G4e8=7G#=QO#__u^dQ^RYdYY(XFSfeB%(hu@gYFmWZr;z zM5>ub%|toD{cwRqbzc%!C6R#Hk+S1Qb>5Chhx(Bsa>x{RfgUdO6I5DM(B`3{^1nXf zy|lezQ&$;F7b>>1x< zWLoeBb0cX_QW;gJuahN7CIc!L?Q7IsWP(ia2De9-Sh!^0^yh5pN9B8;O`;w znXEc#eG}~i1Bs&I8wnk2Xwq6-=WqXZ@$dTP2WGqqb?)l}8Y zIwImB7cXA(Hv}*KA6_CVO2dL6BDpQBCr`fW?imPL4OOqIUcLIhuion)hC`oVdzMCv zZ`y?X;U=5bstxb*1dXR8Bbg^i?oB+;*)7qUw5zd`chi!#CTsxW>t7&TASJJZ2?Z;HoIdePxF`r8+%nPp$wLfPzq3(DxqvWn}zcbrqlXhI6nU1_}-_}>8L)hnO$lb0hLg( zTWBTK(m+}Z0(q?nJXZQYKD-eU&K5jfz?w6WnJTPfQr`!1na`LuHQX?SGpZ%hi6VHw zWv0#yWQrNUcLK943We+&iXf5riitHC1>6ERFk{9#Zgi!1w!KNCPoWE1baoXS9RR4BQpe zoW>uzjMi{Rov1iO+^hH_K8p>$??HiESP~{i@?6D9sx*rm7$z*%+LeFRIm5hx-Ph#e zI@x{q=itqsg13GR4u1>|zkl#Nxb?hq>k_xgsG|cMbPv0-S0~^X;s00S!~Ge5JnY;Ps>{7p34xlqA4o`NsqVOo{H8brpXqKzqJD##k)JS>(7I=>Uak`78PP-Z z)VmeXo4LxCg5B0mF86LG#O|W)dEPm>{ud!n$S-7cP7cn=>wl8bMQ`7`{o9pSFWNZ% M-aY)gi(?h8jYBnN(6F=^k4| zJb3Zq)xSZ!_E2#&XDG2|HA9n7Na7pN}OK=4;?1oftElPwmF literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_sockets.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_sockets.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09c2949af953a79107c3845d3c64b18d674f4ada GIT binary patch literal 31637 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 PlMmfda-#q~rtbWI;o)}d literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_streams.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_streams.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e71c82d43f52bf87b2527c2b3c3a43c7a048e36d GIT binary patch literal 2469 zcmb7GOKclO7@l3*8++}i|wMq{hIdVjTgVXd7ty&2QB-C4~v>p`PIsLt#TsbDW~>x5#z5&31fNgNB?9_A2IY={Go+YX+e zXHeaV^87r6YEaIiKtzUh$2S*kZy}B>_9K$eMG_V4!-302TexryDh#8;;{q_eZ1ZL$ zp!PzO;|1aoYMXHZ8b0lN0jE5cixVa+pD+yIX$sEw@M*7oaT=c}#|0H(X&SajixLy= z$f{KE3GI0QFpRG<0x7+$fFJq+6-W`7yfL9^nn>OD7$=6M#ko?#1L{@aa(U2lh)8il zR4KX4qZqsktR{RC+?1K%EdpN#R^X+rKO^j|NIVjY<)(oDj&JcTgy=XIyP^;5l^(sC z*t0y(^<(u!osi(o^y>r>nG@~0y_-`|<$g+86Ruk>12LcG|M^HPrzO}^6i z^+}5F>S;{Yr^b^1b02Erm_BdG(Sfv0Y-qdWbK}E)$ z13ULZ;{Y_oZjUSgY?ereiyrHv5*u_OaEZrp(_mmnylq$!@K4~R9oHhH{6C!ZQ9{Ml zz%Ww*yyl`qmWWf%hiMUgn1b*_)n)=whG!BX;GufWa02oUr9P$Nj>D0235H=tegGp8 z;7-NmQt=E>G4Uxp1E`!1Q>Meh;*L^;xqi-JMzj!NX)qW4@wabQYS0Fh)n*O1c8;(` z?zd{Qwngv^^~I=Vwetq!r)MtWC5F#C23xFMfJER9&iOa}nzlr|rP^h?USr_pSPN9f z0%0}7YukQJH+@QK$vk9Qs~ryLx<{6Ey>sv-_0$rhLl7qGz;X+1DyT62t@T~=>*j;} z;rpE*CVzS3;pFtAk^S9Q=hjEAb_-WG@?&3Jd60iClAT^3Inyni*~sti=JBIKX;YSl zr=;IY2R6p{KhI?bhn@|g;fedoT50n8;(F=S7nw(;!*_%A(y@n=7k=8aJ~`bj&UCXg zTi_j)@veJgMTUC{N90>eo(P*BS^b)AkuH1}XGzz-FjNJZyHi)c`$!Ezft zRf?JH<{@OzPoo#o#-Ap`{Vhw SBbU1BrM2|s=P99%JpKda@WSc< literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..365e8d3d09f5c61ad2a079ea2db247082444e976 GIT binary patch literal 2356 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 VSbvG}RT7EA`@0^jr75tS{{?&ZuG|0s literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8e3b20db39a60167552b6f75048bdb3a147caf5 GIT binary patch literal 9648 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 F{|hP3gg*cP literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..864c5e4aa8083cc3cc26c7390673b9e82b488d3e GIT binary patch literal 32292 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 BgP8yT literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a778adc275c7d4886841e1240b98f34fae7b460d GIT binary patch literal 32179 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_tasks.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_tasks.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..606935a04cbcf000151fffcd6a752911659dd9e6 GIT binary patch literal 7121 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9fe81ad59488bc428fa7a1d3bd5d10b04bbc92a GIT binary patch literal 7008 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ed3cbfddbc3d60534c0035b6ca3ab0077fa9a4f GIT binary patch literal 28265 zcmeHwd2}4dd1v?BH}3mtg1Ckt!9#RJLZn3S5~%~CMJSRXO2grFgB)@O19uNd0-_y7 z9M9w^{Amw{Iv=wt|1M zzptvh=fDgAN|fzn-2ke;uH&n!ud2SP@2js0|IP3B2zX{9{iiYy3Bv!N594ugKwR8z z5riv(EXZO^h>OFbNU$Yl8Md;wEpCrkL?P}NcIqLnVHXQ=54+jhGwi|J8uP||!#);p%wJa80~+xHeulT!%2H?26UL8-^Pg z?2a|Yn}(Yh?1?qUTZUUiL8(%G$jcjRjkgWA#id~>zHWG3ynVP`6fDC1g6z8_$bO|( zscAs<-n8;M4zFin0fdz;3+rHE8T5`t(O~+ zTaVl!H(s(02jxz=3GWRPmSEQhRJ)*<_J&7C6UlHgni$mv@$EXCRMhZ^m=d(4oqH#e zigx&sw0}rBpFI4?<7#+pOi>ZbKct2u%878~%+SP`612nP3r7xw_8mH~@4>@^_ouA` zqZ4TdoTEuK?Yv(ZRn%xC?Mj?g)L0@ce}HaFnY)vTQ1XIw!bRo z`xg6>{j%ed)9gu(d`xm+&3MXWXD*)nyySA(wZ`&P$nHzdVTW8Pd(ab{`FPwIE3y~< zt_fGLnp!NP;k0%px zbgW1?7MqYFXyv3LOW{#jiU8Xv;PJ6O55qWg3h^Vcu%=04YT|5ER*<|Pmq=nXiAI*< z;nDC(MU~Jq6mP)a7Ga6uvZbhHXD5@FZ#AJ?LzC#543$y15ZZcWmXC{S3HrQ=i! zDt}*It&dA1Glt?n6PUJUnLDtz$n@1u=RTHYl5iwXgg)R)Los={%&A7rFhU#N9)n?X2 z6oV(`_?9Zne)uHk)K8_oAvUr@Y#_{tX$Mabap_zWY?3`-1>&L&;1yv)m=OnOgc*0* zrYW(Jw2jD}cAI@E?J|mycIwTSc5tGn9h`z`D=4Bi0-ffDL*0WtOJkA#V~=TQ`eR4K z@nicH?MyN;cI+tlgLFhqoCXoKV+X@pa^T2e>8vIl1P3{D>=1@0_{09h>BOzHT?8nsOdquwQPdW4TH)l^QSB!G9yq0Pq!~yJlCuHMU|wD{kFvH%0=Obv z5HAQJK>WF2!I#lu!UZe7C_Z~G*zkpv`j;JF)ST?O;J}wYH44H7C%*IrC%#U)v;@nryC6&@5sP%vr6IBn7j!TNJq z7!hUhlIu-9)z$yqwg}a64rMIo; z#Gqv`lJ&=gi;pTQ{IPZ=k4e zc~RfW<-Tu;Z|Y?@Dxm5rt^9+Yw96QhYArn0+PP*#wGGiVYMf`;xJtWr#lrCua`@iK z4TZxwtk41?6zba*OJIqj-3x%n4HJ?>a6W!k__ad|)f?ujH>Ap|uehh(^a@M|=;fdG z)5|yQqnCHuoAYu_yHa&cSDu-E=A#OCO@Q^i8JkMPO1sqY(GV@4)G~ak^>Ah^tl(+O z$e2dG?zqHD?+JyBQcl*FDnE^PZ4aD}{}&bby{hJgs?NEp&P9vcA6T?{t35v!yaCT; z%dL8$x%29y-`suK_5)Xas;p|EtZlBWZPwM6X+nghMJmt5<^hZNx;P-%uC4sf^Zse;d7c|7CgopxNyjbm_X3^?bP`>GqEt5q#fZ%ETJhN8#O9sk@JyI z62eB>P0-nJY#dZHg2Tv`@d+SEpmi?Lx)A7` z3v|v0x))sC*InI9i`4Ubw?qflDa;y==vN%jxxM@K^Yrs$_ zjnRmu&3KGpUnum&@o-EJafU*2A_DI+bi1rL9SW(`b=c&t-c8URa@ay(13_%+XB>_k z;(glp!My&RaMMy@cV)VS%JuWM_8WG8#vw@SQw`0jw)Rx(x>Soqzl|-K3a{OpsTNw- z-K+tW3bbdec;5&#XY3T>5UT4kPJ&!QRZGTAkVmL&&Ugv(2^CElKS2S4$_OeK0`&&g zilz?<>!MX~1Tq#1ttZGOG`6f(4oNRZ<7ZQjR$=q@R6|p$ZGEb>J=Fq&tV=bvW-6-e zEkup>n>B!L1bQ=e^5IkCaJ9FcCLT z=L0o_%9VycS7GEU2!5`@AdO+6-YrjsAuBN@2_?_trxf&c=v_#OL|)mhf9{k5jT_S= zvSpy0hv1IsKy-B(&pnhF4YADnr5>&zlSCsMh`qEo0fA2Q@1gV z2#dOIqZzslE;FyOyy`E0@{F${srm#tv@RMP%A*m9ql`u|08GbWfiA;#!45rz6Z#0Z zEWYZ7Rsx~?Ng9+bDh$h(Gsloo`2|mL;f1UGCm@ytZG%Dg8tAV+j(i#MXDlC}q{0Vm z*+&f#w5FXQrrg!iR>Xg&9)bg~Q|a^zO6;4543HqCjPUOso-+m-6ueR=;24^2HZ+pyufuQy{AeBHdkNq#mS z%%?y5N)lR2RnaKg3t)96i7ZA*bwO1tcz#>_74ay<@jV@wU5)xo^^}^=4YW&{!PRAc^{Jo9WtMB6r#=8(f{TxN5-mGf` zm=rPDvSNmROKC{%DbNSJ>$`8!Y9Vcag=qsECA9%=L|9UH0L4#1{8#&tueysWK@CI< zjB~F&_?m1kQFSKK&m)F9Ko0eT8J8}l_XTUYnBK5FadBB)-A_o`$+<0wnfO53b|R68 zaaPO{6B|%R$k_+SC`(Om^c{cxB(KZH_A_*h~BeRgYA z7wL_?)lVhRuUI10l(^LgsALb4L(HvESY|_!ob&k8nlUs;SgwY!ym2*QIaSw~sq#aL zt%batlGfjBz(*?3ow4H0L}m))!XiOjSR{xGiv)3DksvNC62yf?f+~fI8Y2scjdkQ# zFEqA(NIB_ZH2F1`5Ti?!uv0G~glS6slPF<(Q2`q&%}iCJ{a&JAdrIoK*?ohAlE5XKp~W5c!Iq?p#V@<$ zANJ#`n|+nzDD5YV5_B<#jgR*8hImw zYZ1Rz-X!<2uR4U(DGqrvtYNZctB21PSia==$Xi+bMx;h)Zi~v>$R@_DS3^PQU2=17 zsJxxwX#t*w%ffkaxDopnpG#LC9Rt%ZR7U&=Yr&$*2#v=ss*Zp`f{I;zr~%?ioVJKb zV8>RlnFg{_R6BDUh7$#b5+hvWc3WiDt(-3E41-3 z7>=MCxjF-e_Z(T{NLlRxt6tXT1C@qh1~V(b@;IVMVF^|MF|r$CQew8|hRH4XA2IdUZ^NeH0}%Ew9T3m?PL?Gf7MnPQjckB zA(5p-z9qyb(pId5@aLu#=t&3Jf)An}?`wT*U(*z74fzV}A!|gDBlvst9k00Fx7pf^ zFTU8CbUhelvCVajBxUNz{K*meTZ#=;Q|!NqWQN$^xJ>L{rL<}csXuAX|3WnxC5OrV zc@!eG1EbJ)0FY1zbB02(pNtU@${%L@V3ZbCuoo<6vAvo!#k?s9{9pGALefxZ2nN@G zQ%^;EkwxNrjU1Mzgc`zq!7f|hvc0LVDspiJ?StwjBuq~{hb3g@lV77i|s!<02Qa^8$iAyExT`@mPlbz z66p2k3|d4LCj^9=gVG4QeM+k&6sB!S7z(RN0$s-1A*;CI%7nBd#O#Yp5Sj<}6QQ>Q z4>J%*dCOkd^|f6uZ-0HqD?47_{mSld-uK4Px1XMQ`g+qnAoqp8XSzspz!v0W7$8A01@FR#h$M?JBr*dKuSj?P zl6XNZ)}>`jmMzbH>T}926`sw=1j+86# zf_KXM;=uEP_Zr$48hYm%df)Z!gMzV<6pYoRadtr^0OoXh>h#OCuQ$EY^vxD-Q#0%8 zrsTdU-`4{fH{yPbi0+;xtpgcf9Psb06|a?xdn;|%DtZatCGM@ZUE5s?_S`uYL3-e%>iNAnB=P(Vl^m$FCIy2g-7YU-NZN|#GpN)htKDSXVBx7=;h?5P) z2MW6I=ZTaLAWq)Y_FiRhzOr}0*E{R$O;y!h*)hH2%I@jiSF7i%x@LV{@7FX=KR$iz zYR7y{_d;1WdRN9N_%~9gD(_iZTnBU)8wcve{~!)j+g{rW=vtXLP;0wZZYQ`_g!}(P zE2ZtkCARV<$YoGP%WE21LQOW7Y?`f4thsIOoxT^1QPn+*g&k{~r><_FujyGR>zQ@I zOvY%N;F9_^XS>A&0SKv{KE$b<1#?=lZ-fH7wp{ir93bZ%J#avD2io>FohDD|Awao)yw0;^P z*8+Bet1(3K8STHtws9M6qm^i!X=yTY;&d8Xn=_)=jUO=`#dpt?J z<1+^z98gdua;FdVi>Q8)+2bW@j1mhNj1)o>U!6;FL21F)GUsb~Is5~k#F!KY%GGf3 z#$a6PXKor_))+OnJB>T|!ZJIJ6Mb1bq}jM2dTv#J4W9Wk#jjH{lpYf-86BfoWhO7x z4qaEjIA0T7C=1TIg4x+(gFagj7cw4Ge)=waR+%mO?(A#kTlgtqWyaXI)#f?Xqo2TL+X_VKvfJQfqtdE-4BK^Z_AU$7LT^*XQ+A!NwLSn`wKgG`0u?iIFcg9`_wn_iE`UAtLPXDbFrY)dW=k_OvY}=(O~O&DVU^u1Z=$xe=eq?vV<># zJ|4augLCsahvsoEns0^5;UsLs6U_2eE4nYNZ}k$E!y{P>*;6=Vj++~#2_=bLPFTzG zy(YG=xsgR`GB&Za0o? z33IbGr+oSjs(w1zs2Zy%w~wVt^z#Efe2aB-JbnUuyNMBfzn0>nthuqnQL=r1bbK@! zjVlM}^mD&K2pqx3g%T5VvYA=3qwGWyI>8$a$Do6N*|1s9!QiqL4~;GL%jA&uLZ52c z@{^7oX*gfcm!bLd>}n(O8#E{)K*%RrShAE?la4-5kx|od<%#Jht~@>c^woj+ny$+S zQdZ&9|+O*(noAtG&sv0go%o+VtSGUgBbS{*2&bm4?UXq`0_}Z9$qibpLF64vR zcRS6#eYV%O0fP2yU<dDKw(znR> zH_2gphWRXMO`ZSui7$-+gU**~OY^0Lnx46uo;PadYc{XIndWM`->}cu^evS2&AR&X zS<~hv^|z?73Y=gylVO&MZfhDQxN(ZTpB#yt3Ubzw!~EI_IzY~P6Y4ObP7~@fA=B{H z$i1lE`AKGUTafo1JUZ)QdsON{D%SmQ`1#ptwIIin6n;l)n>D4yit-SlCB-3jIT%c> z9~4qUl9u(V8ipUf^$2;#_Rm0<+wziiW>Mz7&pg7)doJy^Z-! zRzC_AQudlC6emAgE?vF_G?Vp*bw2TAe#)mfZbo%I5zCt;@|%fRzhkvvF>NXf@5u{C zA+!KftJtB;U#gq+jle4ZSVIk?>j*%oldLVJN}7wqm}Q<9uezd`|60SM~z z*#?44~C8&7~<*)Ci0S$r9MXv@u0L{H!h*ux1eKH3aRot@bO2h% zj|0$zU$@+*&_=S{b_ggD&R?%yF4&eMRc^VOKAUn$LPKM!@2(11I%9gW*s0_ z6!HOyHfEdzaS{>4NkkAQ5kZ_p1aT4(#7RUDH`XSo9wyo!H30hP0nu)6TMXFjt&2Xd zy^fLWLyHJMevTZ_j~sS;&yNCjd+;MTJ>)QmIUA7Yu6zINoJ((VD@gd{0rQ4LF$v-)JEr@Sr#OxE_Eu9gT5+*b#P**e6DV9Jm0D!-crWCa12@59A4m&edA5R>F_l^RozZ;IGC$7Z+m<{KLGBdVp&{ALL@KBk|jCHy(~ za1581c<|s5uBNOhT4Ht*(>%0>p}-E=T4_i52{tnC%mrC8R8ZLqvh+)^u&n7o9yu3Y z{ULqQxhGsALT!Oc8f&9cF*OIGlaj1P8 z9|N)vh-513Y)=wIJuZSj^RWZMqL`aVt@U^;KMKnY;4J(c?00|%SF<|!d1j5ut?xYX zbFf8wY3XyYw}3#S#rntmnk{|~cIP`?*QHHHh1C#ZtH;SP$5z%FPW>YUkae?u zu@}h0B!fSNH`f&DOVGRFG1;$z{fX3s*|o>|wPj1v)=5^zA&P3QL7 z_Bq7wImLYz+xJ|bBY3yC&u07Hy>`HVZWG~}yb)CsXh^aF|EmC2GWh-F&t=u}#Z>R; z?1Zo2DQ56X`=%GHZxXA)Q`qIn#*^I!mSb#YW6hdvWx9}w-|SWTkZ_5nWru`6rj{j1 z+_?4&`_Q|mcF%e{-m7d{sO*@l?4XO>aJ$<=dDmQd*Bh>Pg5UiD+NLUiw($pUl0%2( zJF(F82e?`FbGrtd;-88GcH5sh0TtiuB++U`DsQ_}R4&C*)YKB+IWg#2@qf3mR% zZS@3JObOaA7?dS@=>oc59r~T8e&eZy&i=X1{`ty#7JT>2`R-w)O!@B72}$;#Idq!6%kZl=1Vf;2A z*w+>lpK0FBxwDJvUU(Ln*r7_!Us0EO6~HA3vVI7%?ZUcFoR7t|R2g@feL#dj)x8m? zXK$9#*NyU)jGcKqgo@UTlYuTF&}2rIIx>Fd3-PNa!xBOM!XelS@?C6aVeM{`=WqiR z-VkQ!1}cL14O9ft4OEL>K(}fw_FY_Zq2lK80Gh@b51=`-#LlFxfDMz0{rRLugVYML8Hq}kVavAxZTj}#|3%MWi{pZ=KfVN)a%3UCe?{U6 z-yMUbB0X|+Q%pIl#P|ij5H=8M+zrLBa^Zr3OfEC8s^((UacU2CBb44Y#~BLcyb>A5 z4TbD_ojW4@Wc8pAoOkkTXg^|g=StPGnojH5UNJr+)$%2l7N zFiaWsKdWJyE#M~@t1Bc%P``BKQ|cj%@i&CgN(7_bHyU{Q_|7>iZ8Ox#vrKex=@%tw9Yn>UQzo(Q85rYa4RpcOIX$@5BUs5`Tfk6xdg9R$i>v5t9|NlcE6LaGejX&I=%KKx{ytTMX^XZ{4rLL#^Mm~x7p*URBBGKQlD0$>*`xGn zJ6$8KsU$9>9e}}nR2QKkji2fvha?)dothTdA`iQr^dBVp*uTpFGSV%kU9rSDwrpny zEypoY?0ND2mk&-qa!Vli)|V||yk4%mTKh`dErH-$hpZ>9;^vokUj48x1Wf{MI(6Drz%syV#iW)n=@C-@x0BH-uJCb;e1M zOYoMlI1pMLH6ME6hn+f$Cu5<|a>CZsmcnm+vyKf%-0KNj^9{U#t(maZ61HX&TQgy+ zBW%qkwq_{Faa(jVwr^V)sw(mGV#?Q$u@Z0tfSmxshO4s~$P33n;A>g*GH_#V6*Eqo z7-03oo7E3*y?z9_1c&=WHz2(nT@>>+Y|+zwYnruN(i(=PMEL%!LQW(hmv3KhTHqI5;3K zH&_JWiXaQJ7!_jTh$s?liCRW1>}?&fvbSx-hPO3pk2yvhERHSejJZZ!G53g@ecPiY zG0%vH!H%dm<{Rdc8gq{9oxWD z5Ob?sbIG2;C`(3c{+2h%wOP2@jGR}+Ym@7;?Tu2;!fyMoq`dS^1!Q0TM}dvXzkhOcR8do!IpUBj*rpCwPlbVz;qDiBOCnd&02`QpUp~*x%hK>~uMWa(v7_FR8WGOT* zOJQIe2Ru2^U&1gBk0E|I8qzdrLXDq`$O@9@Op2j94@kpf3WF%a z^J9_l7;?$fBaY6l$o%;Tk8<`M7#klX~K- za$(rbtca|+HPS$y$OTRt4ojmbKNUQ$GmnR2xh0%X6*DP^H=I6|7V1_wEN;s}N(_`|{YnfQtRbISO+6AwjBozO4~Y?(k1n1^;E zG(HuHZw-dysWFA!I#lu!bK~- zC_Z~G+VF*x`j;JF)ST?O=)jjgH44H-C%*J$CB9tvqUy6}N?>)HbEy)()lKBIk<(62 z2RWVObdf{t`>6=<01|3M1xE7f`pdQ#+{yYjy!^>J2`^u=wgoS5vaJU%kMU}yD6V8p z(`DN?J&br^lN!KA^?jcD@nryC7akNuP%vS}IAhWZ!TNJq7!_sllItx!*7I0YKLaZM zw&jZTYj(Tv6)};an`p$E$)fa(aK@yifmINW3wH}INbgwBivi0}IOC5A7ax^W$iD;d zEpfd90=?Oc88n46g|9KJGf@TjGU+1A3m|;Gxjys-Uw=X0vVy)9t9{=V-_px)TtL-T zTKNY`QZ8disx|P~Xy=*})mB8;sBxZU<0|Fa6Ai^q$)S6vH|G!MkU|THV6cBrG>#>T zb}s-PH$+H|!1?4^;kS+~Rc&6V+MFz_y5gR7)5|~Wrq^!( zUU_ErnUBle)qd9Z=4>hvE9Fur$Ah$dQcLlv*1?&xu!5&7qZ1nSx|0$weMvBAlybVR zSovwZYy04Q@;|A-?^iZ0Rdy~^b}n1oKL4`SQ&sX)!Q(HvY`Ik@G<9Bm^xJzc+kWJ# zOO{qHm9{RFw$8g+(~XF*vP|W9zG=`Rz99|@wyT=}g|mATIyxUP!vOejxhR|god2Am zn0b$}2F@QcA|?=Yemix3&P*(fCMicK9F1!V$VQEdS>!@En1Ha6auake6rBW^FvJjz z3QCg(r<|K8cY*Qo+SP<(0dXW)mf$e5rM&_O;%`~-w=DTP7yO-z{_Z7L_jOnI$};u* z=URHit74b!cN{(9oU@-lfeKc(>8hM8o;*cT(^I_HmLo0WizzD>tc*FXlxPW6x+o3B zAr-Y#_#tvSOo*{e=EwLZgBbH9DB#G=yCOGlpw@Gy(Z)IQL4p`tW~gpIOXrFeE6fl7 z3C|EcsJ8^1)6LnE7iyF%nBAGoQw~*0OseB48_^3Rg$AZt3kS2#UMfWc^@=C=hcxBT zg|IR~YxSoL^4?1ks&c}Np!--*jvQ+Qu`G^K7H(P)&>AopOkp%)X)_rm*c%Lfc`_8$ zL!7~&91p|06x}W(P6vZ3bsaXjt9KK$j~uoT*h~6c-+g<4{ zp`v}!)^@}0OFIOqJz3wBY;8-nY)Upu^xM#!F8A0y=_;XR)6Hr?Nq<|~iuVnFQ`$}; z4xy?x?Ig%0R5qvG1eFLCO=%B7UZK1(?IXxfP$@xWg1^qdTHg2(VO_Qg4u9H0p>+hg zgoftz$|33HX!v}}(IRZ!nXGS2wzel*+LFy6$fjgNOS-($-b~bJyIBqBhQBv$Cm&7) zf;bfj;#44rQ-L5(1%fyg2&y8=5VmxUP~u;%1+=``ZR!>wxNyya`)v1ahj7vF({e>=K8eno?zK()MDx}q) zi>87^v3v!EbUu<8)Ab4GB3gu`V=5y_gogYqX;T_X+%FwXkW8no)>^Qt4v>ao;N(}S za!hVpO_wY!2I*f&O~EC9rj>}FB1wvsh$Cd0n1L)c1ru>|bUsi+s9b6IOBF`0g5Z}b z4AL0p>)mox7_t&o5>WCyKBl0rL+?ULB=X91{qtiAG;U0f$d-X}4uU(T1JTuGJojLH zJjgN|kb1a+P`ays`;OkgS_Oq-kfq_}V+#|ue(eufn8ceZ%|jk06$d?rd}+##4!}`Nl8a?owZd}bBOx?yTA}s5=jV9mCt*;wpfD_3 z&YnOgd!H$Z>&apcR0KWF(6B^5qo%RXv|fHma|GUcw8vVxtb zyrjs)3Kd!=ObefZWbXpI_7EH>MOuXFdMHGo41pp9`VRW7tl_}T%95TEQjXlJ5xk{K zp2h`F<16Q{d%BYSdoLe+@u8WA=Ib|K_x7f(g14JDILXh(gZcCqUr9o1sY)6}`vI)4 zB$34ksV=B$dC%{PzabunI6lM{=>e;{4H4AiP=(+7-tG9qzJK3$z3M)`V7vdm@FfJqV4&1+`(*OZ3jo;-cP zd*1t&trpS-SeQ1zQB)h?MuZi07f}2h#D8@F`Ko)U64XG%z&Q8Xhp*|TB2{M+{UTzh zgXB;@m~-h;dVipXi|O^N6Bm}%)q{kjjhx$(n28UhY^UP!C}+hiF|h%4l$--_jIz`g zD9as`-5Ez>nVO0N)htNBr5>j8+z*Gz^TQ~WgpU;_&}X(*b&=lKTm4J|{hB3GO^I85 zfJ*itImF!Zg=IDr$+>_(tqDVegykv-%YEw!%gNe?bfphcYz^e)q||=19v?}6ciM_K z6PYQH3yTDCVUZv%EE2?pMS{4nNDvnm391mvtBoumHrA3~ozT$o5#^+d(d5@uM2s#{ z!cM(}5T+^h&!U8F1qE!VG}Dz0_IrtfZAq!)W<5Tbm`$F%Tm*5-5yUA+5T_hLoN@$l z$`QmVM^Ft3+_eM|<=3>E5UOuS^rr3-#-I6^qW6CEpRX4XzF`w% zRqV2jxRBz=7pvtK*)BUavinad<<=ZA+4-6ANwO=;5@5B6ya_`0#!J`6wlqpEm5w;& zb_RPcc}HAwI}^0sc>5r5mna_CkoYgz3@z@67q%o_EPm-FSOnp#n|+nx%P;pZxEz>D zQEMKyG89hUxN}puf3T<)%h9js(w$6azJx|s+Qbv2g zs+Y0(K&4@r!ORMTNP-nWll$fcxVKNcA5Y!^m5GEfEU5LacV-kH4`Ir`7 zI;8+lmRt!+5<=*La&gi+!$67sYrefsJK2Ql|7j}>smC<6kjPRZ*An7WX)D%3_)F6Y z^rXXV!3WWg_qBeuuW1UkhI|F~kTIgj68uZ_9j}<)x7pf^FTU8Cv>lAH(B`^Ek}`E< z{$vUL&4q@lDfZt)GDGZdSS9waQ(85O)Souzf1;X&q1cU3prKh63$O7>_Mh>e}LJi@* zXqT;T+uqVw71_7~_91l(5~svd!vPx?DA;d zINexqyz&k4yNLE=g~a81?Bl<9@f#PD-m({u%p7@fXlCd~-ln9t;sbx>%;}l&MeJw0 zTCTfVRy4M~zkARo{)sqfvHeE}pu*IA6Uf)3W%upV5-Chd0=@p6MT@B7lz>okP#R&k zPid8e!jvrmLt!;Rpvzc0WE3}CnUHb>nSF5)Li4}@BJ@t+VFm(8Pw9($zOm<(op0=V zb=MnvU)}rd``#S>?$dKmUvInz@6Qdo1}nsCCE{S2 z?V85{IEUcGnVzRPU>kBW43MChf_MHyM3O}q5}5&rSD-t8MZ72$>e8|$!ogsn&|GnBFo z4XZ?faCQP3&6Rgf6Q~k$Lk(C1Sk=fWtOhJNW{n=WS`B!Fx)<&F>uNx=;*wK_j&5xwT;ZcuDtq6-E7@uN7Chg(KF+DY48RA`}J*0^}P%A zz3+JsK*88R3dSnZIJ=+{0CPGsbLN$rHyU4U{B|?9shM|mQ*!T&_nZE-8*x8DM0d}M z)`5(lAN1|75wDer`zvhM%6kdkBkr%VUE5m&_y<)Y+?+u~oe}eZxZDo_i-dy1r_fM2 z&TSV#F~&9=e48oLwz}H!Yd~9sP4E;C6V1rr?OEXls(WGP!i&E)^K0|27S?>>>_Lih z@&W%J0IV}#ZAnmU_83E;z>q1Z#Xe&Wa03GkN@L(17ZNVRe+Cw!v?R)e=I6mI6MqGR z&S4s8>GPUOb!N0vpC^c>wmB>Bel`-q_}oUdkc`QVB2L#A9w_L-UnEjKfH*l*+xr!P z#fsh~Z|}UfH(6PGW!LPkD|=`6UaeZJ?3(v>eNf#n`}pjMs~wBg-Akq2=v`^2;OnDK zRo1hzyb0)@Zy2l-|CKmcWqW-)plhY#V2$lsnVsMo5$^vFt(3A8m)OpiAeTWEEvIQ{ z2{qkNv}v|KvEjD4clKU1MrHRr7Iv&{p1QhovASoev}fK0GZ~|80xRk_kY~$3 zjH?|*y!FP#Qf0?NC1`Jsi-qdWHHSqF4T}of>strGXniz7uKDc*S7C_cGTMKRZR0lD zMk~@dv(z{(MMniSt2Vmv)*-vxtdX%$-BoyCyohXn@pzJW$7c>cIG~`6XHOsMmr?x! zv&YNS7)2H^7%7A%wmz5Qg3^+=dBNNKO6W&ki7_b*l&hh_jlsCoFWfY~sxhi>cN%x_ zg=JNU?E2^xUrg7Cdujir=PYC_W}wGCD@F&P-mc9lEZ5d9gaMR2rCf1v0b6 zW_`9GE@V8W{PcbJtTS8m-`Uv$x+cq8dY{OdF>uI+FQ_u%1!s+2YtI_$ucJz=Euc5z zEBg58sHP-R4zi32>x(O*0F-^Y>VVP|SzVFb@C>!d5aO&ii?G#I&6_ogmD}dM+l=*9 z*PCEg+m}kW&%3r~+GWRzwh1V)!fK$Yq{jC8U4X8YV^w9lR$(W&PK5i#*tj3$kf3`;47PGCtKX?^#>w{R%qW zIy%PuTrB9cB}OJQCSy0`Xt4F16iiY+2DaXcKNnL`SwfdVpAx|@awYIZ6;l1Lyz zPWF9fv1o%il-XuQf`l45jU)EhB#rP4*`6wnT`7vp?Z&Y!VQ#i&l~3P6)lVlIRbv(9 z_OVon0e+x|Z?TR~#!g{xH$JNG*HT=RH9K}FLbmUZPL3xcG35}QejYFgfg||1P-2Qs zHZx0hl$}UICwL>FD0C1o8#e1X6j-(5p|PcYl^oJu=u<6Qe$ufc4d?6mGBkIdU2jBw ziv~p)2>C<{OO~=K($VKAGOFvZJTd#km8WN)zB;&A-F5kJvZCfn?`-eOTb8`7^WN5E zW&P!cIir8->h{Iz&ZW}Mc~@uJL-O+tZ!6PpbgeAkg?upk?xfkb-}d?rK+v8IZiDu$ zj^KbexWjgBvz_2=BDp)r%^9J@CN{>NQg$ZtWHYE#32R^GKiyhTRLVq{18J5$$c`Vn zgNmlI0LR!sdDMRkB-zDKe-9q(=Kl`zS80FamuxDVO`TttO@&|@q9tgYvWEVgI2Okp zg29H)2Yz-ui)B4PcbmXKg~#re`wbZNXf@5u>AAj*%oldLVJNJ7wqm}Q<9iczeWLA0SM{~-eEV4+f7P8s_Q=Ci0S$r9MXv@t~AXH!h*ux`IJ;HO8#Kc!Yi2x+FqT$$1}t+S{1w zKOqzDGMI382-OYG-hX*`(N@pTJo5w3tB*ZbdSM4mX52Sx0VVx`v=#3g71e1wg<#vU zIqf6}$8l@ZZh}ha0JMi7FCBpP5yUN#2`VG2<8p#3=m4~i9|xcbzizorp$%lY?GR8R zoWEYbT(B)is_b$#em>=rg!+bL|6K*nL|3-k{mfqbW-TCA6!HOy`qEBj8cIfM~b3F8givmSwNUUdu@Kkwt_bKSvJeCl0&4 z=O=!TQ3A4kid+KcA~i3_7;izQ&+it>rDX; z+tX#;H;nFUJ~j@C%cB4?hq>t%Ic{q#m&n$&4sFXGI-m`^b1$sLZ6kU2&9Ot@F8Die zklT&76F2ykrZQJQ95gPJ3jG}h6q9)J4?t&*K4)&9GHz-!?nWa?FmpFs?gehTAoXc( zYNPYdEDMs;vJOJCa+;-KhQs`1@)8&wCY+ny~>7AtKS)RoV{rQdfn@>=?U8 z>{Fvb4qSl7;X+(wlT}yfhjoMJLQM_{S#Rx}QbkUW!%P}>fw&WHaw2G)ugyB3%VX#l zh)MFKN{y+31RI%m=7KC4DyYl_S^6bdSk`nPkE{!?{*XTD+!HPlp|(IJjkQs+ zn3@C8Nl9Xydh5aWbmQvIZW{0YEiLM|A{Hz$D}=5-7~Xi2oxSYU)=6eK$<6^X!fay~ zVZGqFv1cFd!}4+K6q=ZghnU#&L@LrKetk`AO6byvxOq=uy&O*GTE8JCsAB9XWOOF; zGr6U}U~1fq__$_DFDDTY4K5+$I_@wuu7HAsE->b{nk3PXO76L^73iwkHXq9v8-+ z`PczrQOr%GHhMgkABE)xa29?J_ItpCt63fVJhMjS)_0!xIoN`|wD>vLTR@=EV*L|- z%@#igyZha)>s7nC>6<67!fFt))sy6yV=H3~r~VNF$huj-*bC%glEI(Cn`?^nCFtGo zm~LLPwEu(3_8r9IqVkQJhAU@h&nER3H$uAUw6&F7cHAn-KUckDssG4A|B=gw=PNfa zczZwa__L2)x3<6W%&X5_KK#-y7(jo}(!JEOZJ}k`d*0nJo@*uJxq335^WAa?HLXk4 z-6Uh%zU!OwE%n^D&~x8n^}eOjeekwja2@K=5Ai zfX()Ud+mV#+$O>`c_XSO(2!&U{x<=vW$^p!U&yHC3#s1G*$H3XQ^??#_DwHZ-y&9n zC%?;+jVHSeEX&x;#+o(L%5)(UzuBw!A>krT%MJ;DLM=;@xN+?l_M!LA?49>?ykF6{ zRMD|e(Loov;dZyBvaW@)t~Xup2EP9#v`wWSZR7LbB!>>mcVeOG^K-N6=k^Rb#Xl7X z?Y2L20xG=ONut%7RNir?s9cPtY>2*2eNta#3Hj$J|8zqE+Ug0cm=d&KF(^y+(s^{f zI{bT2{mxTModXM<1B(^+EP3x)@ZQ5nne^VH6O!!H|CsF4|HKJs&Pun9c^Mm6@$pMa z4MhRd`TPa4WPK21+k{P>I3J5^snYIJ`=AJcs=E)TXK$9$*Nw8~w4Hf7gz}cO zlYuV5-)KgbI?_Jo3-PNu%@RTW!Xeml@?CCYVQp@b=WqiR-VkQ!1}cL14O9ft4OGh> zK(}fv_B~v3q2lK70Gh@b51=`-$j+oKj}4QF{e^@^gVq}kVXL|E&B`R?fVNB>j!uXZk$A6xPqo3|aC_Z(Z%Xv99(yHk8e z++q8DJGS(!@GzbYjBE9e5m}=|hO|M0SZ0Ixhw$B&JG@G8i>N-lK66GY;b+dS{7igh z1u`*LDzwhHQ)4)56;gZzzp?QVoRML(hH(UkpeI6!v269p8pD)P|MMEAnF4-_vARZL z1Pw^XKBFGO7=ObUtwb=|eWQV=kMFFr@@7ej4>{JoR(0=8uiHnxt`~iYk2tBwh+8aw zV9E2qyzPN`&jb2++kBV!wz%E)j{Pn%jP&M1L*jXov19Hk$|YSE_; zA|17|K}M5kncLxWd8xLc9(r1Vbq3R9+-@f=*1nliWQs`tK7qDhz&T(pIS}5{%uE=TUyB9j{UM%0WZE+codmrC;>hy-l1G`)&WuzU}AV!Z+_| z3)8g6w(xGVsZV{*ZJk}F)$?b{lWFKv`7Ql7)Y9b8&GtoGde=h7uEp}*OP<~Hw%zld z-7D%MlHhu`p`pWp627`UM3|_n@LlFE6Z$U%F`0rSiRn_dJa`g2mVMMkpVHH>c=>%3 z`nAps(ce-^hKNCVh|04Nv9ty%^)DzL!@wYhfkAl~YO^q)C=iS|9`!#10pywv!gI(( z#-L2Sm|R4%kP(v=vXi3c6`aNV{j8X*Ff1m6G6iH5@?@1o{Yy%w3tkLGC*7kwI*BWt zagVY}Y*YOMa+qpo8=zEam@epG|8qs_51k6DBr~Kek$C3Zd&*9CK5Hti?NbiG;1sHh z(2#mgEg^@*54QK264;6iTa)zf9{Sn;wg56x1g2ck_<6R*W~bSpJYsmq6q(x}R1gkQ zQK@VTyN00SyaY7V(Ub_F4 z!?Tav5(vKaRZ9r3S8A`;yxMw8Ao$i1>uIaF^_AUMe|4d;@0LIxw}zefiF=G#0MiF8 zf~Dj}eRC4OH4Vw~nzY+4HYB|@X)E40@V4U(>9V9M?Ig%0cuH9uNSBW4k38_hmYSs` zZK2RI!q(WD#BW`bjtytg>j+!Z4ZMM^iLliWwk8u>6Je_*Y)vM%CTO#9t#cFhS(_QE zO7V+g(p#Ul5^w{6odCjyJFXeX3&%jW8;pKZ0C>!~KyP zkX{b*^JmwJ5R|bh;mxXqw_YWJfWSit%1i`hAd69vw6jcnkk^6$Rtp03S}+h8%9q^) RE_b=b-M8x8;s6ug|1VPqPznG5 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_testing.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_testing.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11363556769b3e677eac514e934f6d3f23704542 GIT binary patch literal 3702 zcmaJEU2GgjdFF2Se)i5jCvkpE?JyWO%BJq6DQ!}m1_%i@ZYw9$*v zH}icn^Ue3~Klb!!1im$EY;HV9$lr0&`IH!;JMRFpLTqAFkJM;|Qp8hUs*;wYQd6xI zCACZ?D=BF&SIbxOHLaq_x#AUS#Y(Z(Q|W=ZYG=IOTB%Z^Bt<5OoxMx!+{09SSEbJ` z+WEUm<$&E|YcL+Pd+h>@hngw9^cgbgG%A?BAB1M;2ELfmQ&Ilng6W3ljORr83CDN1 z83r8I(--|_q*$g1Kcn%^12>(}Fh$e1j2Y9q<@mN0UmU}>6m;hen6D6rRH#iVDVxGe zQxzpocK_LVI}Ng_wq`3ZX6%Bk!kC3OWMIs}8?rFwn>oEV%DrWZTa*55U`~QC8iVgm zr_LSW_@Q8?&!kv$Jn(~tU?DJLv#cqqFU4{+K~tHp<*tkfIrHha1CY-C9hEWUbhUXYY zR5Xm+4b#Jy83r%He%^zi7eNU@9{@cQDZ=q)_oGG1Y{Rz%3W6TU*yF$&tA1BFTvVsc zTJ^FcZiPX;It|XkuJQnNE~?`YLKm-2vO9u}dmvQx+Za5YT@L1hYWa@i->JUg&QygP zI?vaEWZrZ{6`aWpss77PtkEf5 zi8NQZz6ed@R`;A5#HVBHDUJ`1)BvKJ3lxPhehil~hT+@>Rh7C_ zg$pA<6wd-!CVws*e&6}!4?ZcJ*gAA{<=p*qzi=MReK5Cv=tQe{;?vSlD>uZy0Gw9x zT$rHGN>z*kvP%_YzZiJf-aAeBAZ&o#s?3>UPQGT1O6di%w50|UBYBUBY~d%yj}l_9&Y^*&755SH_aot~Tql=MfL zX`(Ari^@sYzOa}bm5J>_M0K!a-Y_iieF0$z&ufREj6NB7Uxt(RsDMLy-=l(f>3QTW z16U?o2al|j@0T|Z>gxyf)`3&2Bdg(W7SpUBanlpHL;B-_E1Y&I!c)iB2;ckIV{n|&-fwgty7;BUZ~qs|3n03{RJcsGHPUxj zzH&;>#gUSRY9rIzqJ|Z4iA@Di-%;a>8dje60{YXUp49$pQ20k#2V4iPq(eZ`bzR|K zgdI^PE-F!mOI1b6Ob~bwji-Ug(MoMRO2b{9bre|FI0QW5{Rj>Nh_Z%Z2NpO7_=)g> z5d4G>AeA8)L7*eRc;n9@z?hUl9w!FO4te;AX#jW0=c$~U+wLa^&M!}HD@FA({adlL zts=6WA^pdRTf;3e3)#c z3%v+PcOrP$W|;@--mgQ0XrYVWlMdH*HgvFd`P$XP#0nl`Cc_YrO;7A~?arlpPu%u^ zx=|hc5Q04llWPcGfuF$KDeFaXXrpj^vv7L7aC)O~rlp*TBL>LHhxJ%KcN~!J=h`$( z9@=@{d{VuW{iH+dlnGElYSHgjfyq2q$VP=Xd>M zRv-Ih_$wR3FK-S{tPf9Y3{P$pUTZ0@J*`Gm)w|e4T?OQILU*aMqoV)kIqyPK^&>dt z=c$$SPgE67Xwq}!J@wb=1x#Wue;*op=yOBYW2WcDiKwkdXlfbu`>5I3fHM))oNx}C z3;16k*1-{^8?Z4}fHC6*(688g74cONo+H3^UpxcgU*tc_A6lSidJy);+gB987G zWuTD<3isUafsmy;y9D8A=UC6t|cid%DoylpCe*F>7)J>JtvR+FO8yZ%m4rY literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e9f06eb801f22a79040aac0fafa2dbdbf49df2b GIT binary patch literal 3588 zcmaJDU2GgjdFF2Ser=zV*hx&CFc{a$rtY~dZCsp&DkQRTTREXFKe}94uXoq++Izc~ znY~zdxX7XribRo`7fe-3UTsB)S02j~NW7eJC0i>HNEAWeTwNqkp7_4myUY1v*wcOU z&3xa?eDnSPv9~uv;M+E*mZwvM{2eE)Pl*uPSO#R1Sj3_(DbWI@h{xPmDPD+|l!7AX zaaS!R3W-v(kc7Enscx#2E~F`mkr`qo?h-5cFczIy$XGpA>aJ4ATD?{p#vZHB%D~uL z&1$*Nkx8Rr*6=((FapQ-gcb|am)8s@Fcw`qOwZV!&5gk4upYncRYS!zMDRI{HXgfW z2f87uo~bVy=Bn*kW^``^n?lgWTQJ`wHYrex6k--F#I0CCNt44rD{aL=G}X#j3XBOW zYpF0M;Ry*CQ}Bc&jOl7h>kCuw7-DtSTk?%r5GKRmd)qE^Ti9M8nBg%g)-v}!zam%w z%=wJunsV+KoH<^=9BYaN%QmZc&h3f~Q`qV(IW}khk|f}+jy+|A1Bc$)_B4OQATJwd z&Jsvbjkd#$LA7kNjuWnxQQd}n10=cGaft{x^Nf-miDuctb!Q z*EXdQ^Twi?zv6Sh5;&gCOu(Y^5R2CQ#T8)gJXD^gsiCUl{b9_p!gO2UP?3uY9$=~+ zMvemxf7-YwZ5*^o@2czSO49?{i-3Ft&JC2N%O9qE9A76oo+!U{3bQufdJJamo7a&u z(dJZOmTb+cM2pRZG_R$?esoZ?!lmKRoMA@SOTJZcZCww0bp3Y4aPeij&U;`#??upu zAcvqIfR+dqVY^F5(L!aZ;+X;kK@Vi?v43T%_#I($QJgnQ#jCbh4g7L(9-M_;=RWFO z6sIACE?=KzcLbYuL8#)lFnBn->aY04{2kl7Q+&%=ED9&EpDzQ+xMhnXIFsW~=wJYQ zLJw>Kmc5lPSHqO9JDwBhdhJ*%B=W7LlOUJ40$_tY>LdN^p0a;>_(AT&T>aF!ExI+j zcmCqe`HOY!wff+td&>Kn{Zm6QB=?`q!_c?SMqxYBEGmdaP5g49n^DNvYZ4&m) z`N3?d?Aj%WPutRB93LL4K14ScC<H#)qBQ1%blsG&hRe*r`dikOwebgD#ighqzZCW3_NV^ zohN)4Hb8DwmJP8iU$af6^a5F$QiF+++-2fu={&FrbW@N#cjAMqn=|)ke)?`bHT<-Y zs7)zkRAs;(u+aS-C9VwFBdzyQ%!067Y1`?^X+Vj8oR}w?GFMa1Io73Ga$F|1OCi<3 zl4)Hx!S@A(Av~`cf-?GK;C&TN>QVuR^xma{Xz6+6%>&pV`^Qgi=I`hCj%z!|wfeF1 zTccaSZ`QWgwpM?8>XU)r5B+ZFliojkyPmsNPhERjK`6ackOIgisZuRg(`8!3(0$iXtTr)kdbbS_UiNI-B#M zzN5x#8LT|b1@xzy)~@|Gpzx2g7Ptvq?G6D+H#LQS8Fqw;sHlVqE>#sOi@xtdG)@4K zqm`O?7>B!B>nO06;SlhI45GHlq@=b6K@Dt(s0r&|YLMlTria^lv@60~L{j1Q|Gckd$bO`-UFao1IupUeCd&#?yT29T4e%M5UY8e zDlnMq2-&FciswN@Nks_g;AHr+q_xF14Os>CXpAxH{mZZ5F+jM*ZbM0BVXGcd1Y^8W@lt(cVu=q z`$k=P<7qXbs@}sU>N+5A5_(9LEfxJg&v_4;svp8BKaXw3f1;{zLX}RE_tjs=*D#5_ z@;zwiq0bE*ml>`bC8DMtp{Zrq@1tgye9nYlvV&!8F6Np(6P`r64jUr{=!>oo{fgDC zh_8e2909ib;u!${BLCSSpJkre&79fGyu6cnc{g)mPr2|X<-%iu?beTn$LO!WTwB7I&js;2@6?RKc zK}U%K0(JdD?#gqR&M5 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd6b70952919ef240f9a9bb425047cdce7920130 GIT binary patch literal 3967 zcma)9YitzP6}~h3Ua!4=;KfcVxCBC2YBn?}w6*#OwL_siiejjgWzl4I<{A&4omuXk zwb|7YS;%S~kw{Ucibzc=epNyI>EB9Km7kT`A5auwMpUJ0t1ACY90`H$hv1nbdP>L+A69l*vtyLD7vrs!})oa4?*nuWo=@*%(=}y~nYntJ5R@0;+s7IyQ_KR|- z#cJ^pP^2IJ1jHCwBLD6iy8hjzpYb8sZE_8`1&6Y}2j*Xba~P>ACaJ2X0>PG8A+w69 zE*GlF6>>$Xrod52e`xbV%rLL>i#&xf(`qtqfH{KBx_qmvsNvc+7>}V)$;%I>=9n=r zs5hs3l)-biX|q+gEu-gO>duBYqE!!A&*=i*2o0va;HpS|M*Om1O&b2dJZ1fNu=usI z@!h+CEQ{O@jlZgR;fjzhiBK?2F1$p@Rkf>LN&$l{!&DiHP!=k|bU>x_>9pCGU;)Vo)J%Qcj)ri<;ImOK6&}Xq`Mpw~Tt)*-0%y z8tz0IB|r~S-D29N_hButC@dEx2Y6N{EN2LJ)$05d6pujLPEn&nuY7P(i` z5Rir~IQqoTA|FR@Fh8eh(*K$!Fi*jAFY#em@EtI9c0Usnm1h`_1EHwF^mK_l+4Y8C)X3|_Te?64h^rR$oQ@=3M4o1PUdcQ*QeRtGQ=U#q`5@1pFHh441AK+ zG&BSJnb)+2Yqo4$7c}j1OSi+DjHa2cp=r`2zdtnCV73ijQTQ&@A0zCwz;Jjx!f`@3 z2%MfSlgDZ@m3>mkruIB0FiCYJkpcJdh%-I9V>9Sj(#!zll2LA|rdZm{nu!f3892$A z$xSC&mIJ8f&D3%#{xZ`}m;3zE=BI%av;e7yccy-<3r2fzl`9saanTZUnDd$eb%Atbq|O01R0 z;P6T=s1jj`hudJtm&Lff0g39Cayn2&19 z8k8MFeG?G0NUkK_B#TNpq5o+klB8$%#crRVq4_^(Ik2)EW|o5*3$CDHXM=!~V2RLo z(SlCntFgTy=q8&w*Bd?9Ps-4aMbmu^)~Ewo0Ma_0lxbx0t+zM7IW!4xl>yvw;G)`4H*}Yi6ojDb1k<@OM`v}*#ug9+f#^`zm@CQ(R9=KWMLM|m4xN!BaUq9n6sUW; zoa6`K)&1-V)(&cOE`CJb2RA@~D3fu{Uq#Y1RCo_MObjKI`2Pm`B+!eKFfEa_9qHW8 z)zR_$qxuJfV0kvVUK@4=+7r;)cv17 ze_?_KhZin@d5B<+Y~*)xRZed>MWZ?PvjN}X;8WTK9@7ErBXX=g(nq`)k;_#%&-dWL zi#YAY2@{{6g=zqq-fPPz;Mgs5%o!Z}G=3N-Ou0cBml@F4*`XrJQwcHJxb+Q5C3SLlu~1O)c070RUihLhaTi; zPI(-Amf05kI)R=g?91vc%l4o|_=VS+Z7tr7Yt6RDgjazk9L$1VyTaKbho2FUqoX4~ z6P@MZVtID<~`Jw{-7fes~98e&PVUoW6ACPt# Av;Y7A literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc b/.venv/Lib/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a592509bb75505c19c8040ccf255eebff878688 GIT binary patch literal 3853 zcma)9U2GKB6~6Pc|7)**V#g#ETmm61HHIdIwpP=Y;!r4rq8KV=Su`2XT;svBGt0fR zHoICP3t6or5-F-w5ve~yeW-$X>1$uAJXUHSpeTwNQI)E#s=S#v5-3kS=g!aCCW?Bc zy}CbV&fh)XIcNXU*H;ot)Z}4i*6~a?%=-(ByS{0Eidq7vZAf4r2?FlS zv;ua@_vc&9*Esiim_2LYd004WOtD$BKoRDzm`7GeT4%SM#9$U4gV;_}hS`Z>Y4 zs85@X`V6CxTuTLy6Z=rt5 znW+mWVEdbJWnN^WZhCFUKVVosX9oY=;%OGg7tqFeuUoKLLS6mdL+LgV(>k z^b-RiCW`WdqjSuf7c`hN1IpmJ+qBu5aUHAYU+T}s zH=;EYv7R#pyb&5qd$Bbk`B~}9f;CzAfqBaM?_u#vW#hYd0X`179UFgD3E~wYU6O!c zid=Y!kgIA}y_5k4TZXMBbX<9Nq^ow71w|k^PwwB* zy5zl)UJNRvO{$sGVZ|_dW(mUx6{Azc=$27WJ3FW&Xu}Z#utS{yytmmC)ohr%$N+JG z2hDbPSQLTD1F`4?a{v$^jxduE_*&thVK|->7>Rog0|9Bdf}>BuJo53(1`7*@A^mR{ z0+SIu_Yxn11>X)+XV+6PG4T}RZ9pjMFg;l!5BBW;(7N-=)JHS-_MBKLJs5lbgQLIA zuN+?;e*Tl;J$Hxq+!P-VfBg^nk4k?#c=EyMj@40mbL?jM&iKhY&zxG<)Mtm*h&nj5 zmLc1AeqJVp{&#YB^E*Gz?~)-7i6+Y>nnV4x?=kR6-Z0P%@MqC58ou3faa}fy%PrH5 zZ*qoV`<7uylfu5(V2imfctznmQGblEA4G=3;}MP%x;EGzFtQxXKla(YWY{Im~&@h&skHyAp7-2Tn1u32^8d2pVM_k#~bQ z1k(1#SH=B?%pbpJJicOBkCCfBrLeAoTBg#ei0f2*wIltxFsUXu8KAmCI@hrDx zLm6;9$s^Jb_K9(Op+L!-gi*t6Q>#s|Q) zP;qp@+*uimRK{XBARXB&0HX8Kmp}W&RzgT}NtIX|B?Ch%g{Vrz6&`M*Azv2b_68)X zTgvH36_t;}QCR4?5x)aP5e{KS3hR9Bt5o?W5DLt&OJw!sLy>GuJsZa++57-pMbb4X zogajQFlR@VU*N~+Dw&+V0h_Ou|1F?MvX%SxDP%xC+h#(lYdJW2srM?LWS|nGrH_4(>)yzL_ zM3VIE-qh{mG&cX$mIo`xV|F#FvET|CcQyz(36=zX7aiy{z8u>df^M>DaMCLIg)d zV2U_rmU@0{6x798AIy)Mj}0J0S*f}`Vcc{CAhC&CyxY zaj^viK_EKTHRX!3B9#}RPmvDpy+ddCNLt7t8%64#uIhY0yn2{F$=XqEE~JmhOK<}O zh%y=H{1qh4LWK{Y!^BWRiT`h~PXfI-1=A8)+nz1#SRL8+$;iIDBl|x5&fSqiE5{!7 zlY!CeS5_)dKJOy~W1kTnXey(>uHLQedN6w6&cWC2jUM~+)SZDd51-xsi~h9&+`~^7cycqZR=cE37oF?7>`STaXX>@qu0+@#g=Ez2VCs)<% zhEp_}V?P`59g04sUEnbl!9F3!+9Q3$D+#$=Rf~K#9=wRt9-J`og?Xq3km-ZAd;*T$ zGRK_3u}|ZNal(`vm2sI-_Td&MWG8+M(=xfQzy2`O_nBJE46SP<+rNgZwJ}(Q`XPC; zsviQ1P(K&%&OOW&R}X)Gos4G2@Tl}ig;gX5nI|sfXis$*o0i!Y{5^rDC2Gtn8@p+9 z#^M+X9P?&KT0>seKK=5ev2K%)fH None: + self._thread = threading.Thread(target=self.run, name="AnyIO socket selector") + self._selector = DefaultSelector() + self._send, self._receive = socket.socketpair() + self._send.setblocking(False) + self._receive.setblocking(False) + # This somewhat reduces the amount of memory wasted queueing up data + # for wakeups. With these settings, maximum number of 1-byte sends + # before getting BlockingIOError: + # Linux 4.8: 6 + # macOS (darwin 15.5): 1 + # Windows 10: 525347 + # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send + # blocking, even on non-blocking sockets, so don't do that.) + self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1) + self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1) + # On Windows this is a TCP socket so this might matter. On other + # platforms this fails b/c AF_UNIX sockets aren't actually TCP. + try: + self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except OSError: + pass + + self._selector.register(self._receive, EVENT_READ) + self._closed = False + + def start(self) -> None: + self._thread.start() + threading._register_atexit(self._stop) # type: ignore[attr-defined] + + def _stop(self) -> None: + global _selector + self._closed = True + self._notify_self() + self._send.close() + self._thread.join() + self._selector.unregister(self._receive) + self._receive.close() + self._selector.close() + _selector = None + assert not self._selector.get_map(), ( + "selector still has registered file descriptors after shutdown" + ) + + def _notify_self(self) -> None: + try: + self._send.send(b"\x00") + except BlockingIOError: + pass + + def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: + loop = asyncio.get_running_loop() + try: + key = self._selector.get_key(fd) + except KeyError: + self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)}) + else: + if EVENT_READ in key.data: + raise ValueError( + "this file descriptor is already registered for reading" + ) + + key.data[EVENT_READ] = loop, callback + self._selector.modify(fd, key.events | EVENT_READ, key.data) + + self._notify_self() + + def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: + loop = asyncio.get_running_loop() + try: + key = self._selector.get_key(fd) + except KeyError: + self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)}) + else: + if EVENT_WRITE in key.data: + raise ValueError( + "this file descriptor is already registered for writing" + ) + + key.data[EVENT_WRITE] = loop, callback + self._selector.modify(fd, key.events | EVENT_WRITE, key.data) + + self._notify_self() + + def remove_reader(self, fd: FileDescriptorLike) -> bool: + try: + key = self._selector.get_key(fd) + except KeyError: + return False + + if new_events := key.events ^ EVENT_READ: + del key.data[EVENT_READ] + self._selector.modify(fd, new_events, key.data) + else: + self._selector.unregister(fd) + + return True + + def remove_writer(self, fd: FileDescriptorLike) -> bool: + try: + key = self._selector.get_key(fd) + except KeyError: + return False + + if new_events := key.events ^ EVENT_WRITE: + del key.data[EVENT_WRITE] + self._selector.modify(fd, new_events, key.data) + else: + self._selector.unregister(fd) + + return True + + def run(self) -> None: + while not self._closed: + for key, events in self._selector.select(): + if key.fileobj is self._receive: + try: + while self._receive.recv(4096): + pass + except BlockingIOError: + pass + + continue + + if events & EVENT_READ: + loop, callback = key.data[EVENT_READ] + self.remove_reader(key.fd) + try: + loop.call_soon_threadsafe(callback) + except RuntimeError: + pass # the loop was already closed + + if events & EVENT_WRITE: + loop, callback = key.data[EVENT_WRITE] + self.remove_writer(key.fd) + try: + loop.call_soon_threadsafe(callback) + except RuntimeError: + pass # the loop was already closed + + +def get_selector() -> Selector: + global _selector + + with _selector_lock: + if _selector is None: + _selector = Selector() + _selector.start() + + return _selector diff --git a/.venv/Lib/site-packages/anyio/_core/_eventloop.py b/.venv/Lib/site-packages/anyio/_core/_eventloop.py new file mode 100644 index 0000000..6dcb458 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_eventloop.py @@ -0,0 +1,166 @@ +from __future__ import annotations + +import math +import sys +import threading +from collections.abc import Awaitable, Callable, Generator +from contextlib import contextmanager +from importlib import import_module +from typing import TYPE_CHECKING, Any, TypeVar + +import sniffio + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if TYPE_CHECKING: + from ..abc import AsyncBackend + +# This must be updated when new backends are introduced +BACKENDS = "asyncio", "trio" + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +threadlocals = threading.local() +loaded_backends: dict[str, type[AsyncBackend]] = {} + + +def run( + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + backend: str = "asyncio", + backend_options: dict[str, Any] | None = None, +) -> T_Retval: + """ + Run the given coroutine function in an asynchronous event loop. + + The current thread must not be already running an event loop. + + :param func: a coroutine function + :param args: positional arguments to ``func`` + :param backend: name of the asynchronous event loop implementation – currently + either ``asyncio`` or ``trio`` + :param backend_options: keyword arguments to call the backend ``run()`` + implementation with (documented :ref:`here `) + :return: the return value of the coroutine function + :raises RuntimeError: if an asynchronous event loop is already running in this + thread + :raises LookupError: if the named backend is not found + + """ + try: + asynclib_name = sniffio.current_async_library() + except sniffio.AsyncLibraryNotFoundError: + pass + else: + raise RuntimeError(f"Already running {asynclib_name} in this thread") + + try: + async_backend = get_async_backend(backend) + except ImportError as exc: + raise LookupError(f"No such backend: {backend}") from exc + + token = None + if sniffio.current_async_library_cvar.get(None) is None: + # Since we're in control of the event loop, we can cache the name of the async + # library + token = sniffio.current_async_library_cvar.set(backend) + + try: + backend_options = backend_options or {} + return async_backend.run(func, args, {}, backend_options) + finally: + if token: + sniffio.current_async_library_cvar.reset(token) + + +async def sleep(delay: float) -> None: + """ + Pause the current task for the specified duration. + + :param delay: the duration, in seconds + + """ + return await get_async_backend().sleep(delay) + + +async def sleep_forever() -> None: + """ + Pause the current task until it's cancelled. + + This is a shortcut for ``sleep(math.inf)``. + + .. versionadded:: 3.1 + + """ + await sleep(math.inf) + + +async def sleep_until(deadline: float) -> None: + """ + Pause the current task until the given time. + + :param deadline: the absolute time to wake up at (according to the internal + monotonic clock of the event loop) + + .. versionadded:: 3.1 + + """ + now = current_time() + await sleep(max(deadline - now, 0)) + + +def current_time() -> float: + """ + Return the current value of the event loop's internal clock. + + :return: the clock value (seconds) + + """ + return get_async_backend().current_time() + + +def get_all_backends() -> tuple[str, ...]: + """Return a tuple of the names of all built-in backends.""" + return BACKENDS + + +def get_cancelled_exc_class() -> type[BaseException]: + """Return the current async library's cancellation exception class.""" + return get_async_backend().cancelled_exception_class() + + +# +# Private API +# + + +@contextmanager +def claim_worker_thread( + backend_class: type[AsyncBackend], token: object +) -> Generator[Any, None, None]: + threadlocals.current_async_backend = backend_class + threadlocals.current_token = token + try: + yield + finally: + del threadlocals.current_async_backend + del threadlocals.current_token + + +def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]: + if asynclib_name is None: + asynclib_name = sniffio.current_async_library() + + # We use our own dict instead of sys.modules to get the already imported back-end + # class because the appropriate modules in sys.modules could potentially be only + # partially initialized + try: + return loaded_backends[asynclib_name] + except KeyError: + module = import_module(f"anyio._backends._{asynclib_name}") + loaded_backends[asynclib_name] = module.backend_class + return module.backend_class diff --git a/.venv/Lib/site-packages/anyio/_core/_exceptions.py b/.venv/Lib/site-packages/anyio/_core/_exceptions.py new file mode 100644 index 0000000..16b9448 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_exceptions.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import sys +from collections.abc import Generator +from textwrap import dedent +from typing import Any + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + + +class BrokenResourceError(Exception): + """ + Raised when trying to use a resource that has been rendered unusable due to external + causes (e.g. a send stream whose peer has disconnected). + """ + + +class BrokenWorkerProcess(Exception): + """ + Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or + otherwise misbehaves. + """ + + +class BrokenWorkerIntepreter(Exception): + """ + Raised by :meth:`~anyio.to_interpreter.run_sync` if an unexpected exception is + raised in the subinterpreter. + """ + + def __init__(self, excinfo: Any): + # This was adapted from concurrent.futures.interpreter.ExecutionFailed + msg = excinfo.formatted + if not msg: + if excinfo.type and excinfo.msg: + msg = f"{excinfo.type.__name__}: {excinfo.msg}" + else: + msg = excinfo.type.__name__ or excinfo.msg + + super().__init__(msg) + self.excinfo = excinfo + + def __str__(self) -> str: + try: + formatted = self.excinfo.errdisplay + except Exception: + return super().__str__() + else: + return dedent( + f""" + {super().__str__()} + + Uncaught in the interpreter: + + {formatted} + """.strip() + ) + + +class BusyResourceError(Exception): + """ + Raised when two tasks are trying to read from or write to the same resource + concurrently. + """ + + def __init__(self, action: str): + super().__init__(f"Another task is already {action} this resource") + + +class ClosedResourceError(Exception): + """Raised when trying to use a resource that has been closed.""" + + +class DelimiterNotFound(Exception): + """ + Raised during + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the + maximum number of bytes has been read without the delimiter being found. + """ + + def __init__(self, max_bytes: int) -> None: + super().__init__( + f"The delimiter was not found among the first {max_bytes} bytes" + ) + + +class EndOfStream(Exception): + """ + Raised when trying to read from a stream that has been closed from the other end. + """ + + +class IncompleteRead(Exception): + """ + Raised during + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_exactly` or + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the + connection is closed before the requested amount of bytes has been read. + """ + + def __init__(self) -> None: + super().__init__( + "The stream was closed before the read operation could be completed" + ) + + +class TypedAttributeLookupError(LookupError): + """ + Raised by :meth:`~anyio.TypedAttributeProvider.extra` when the given typed attribute + is not found and no default value has been given. + """ + + +class WouldBlock(Exception): + """Raised by ``X_nowait`` functions if ``X()`` would block.""" + + +def iterate_exceptions( + exception: BaseException, +) -> Generator[BaseException, None, None]: + if isinstance(exception, BaseExceptionGroup): + for exc in exception.exceptions: + yield from iterate_exceptions(exc) + else: + yield exception diff --git a/.venv/Lib/site-packages/anyio/_core/_fileio.py b/.venv/Lib/site-packages/anyio/_core/_fileio.py new file mode 100644 index 0000000..a0d6198 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_fileio.py @@ -0,0 +1,742 @@ +from __future__ import annotations + +import os +import pathlib +import sys +from collections.abc import ( + AsyncIterator, + Callable, + Iterable, + Iterator, + Sequence, +) +from dataclasses import dataclass +from functools import partial +from os import PathLike +from typing import ( + IO, + TYPE_CHECKING, + Any, + AnyStr, + ClassVar, + Final, + Generic, + overload, +) + +from .. import to_thread +from ..abc import AsyncResource + +if TYPE_CHECKING: + from types import ModuleType + + from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer +else: + ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object + + +class AsyncFile(AsyncResource, Generic[AnyStr]): + """ + An asynchronous file object. + + This class wraps a standard file object and provides async friendly versions of the + following blocking methods (where available on the original file object): + + * read + * read1 + * readline + * readlines + * readinto + * readinto1 + * write + * writelines + * truncate + * seek + * tell + * flush + + All other methods are directly passed through. + + This class supports the asynchronous context manager protocol which closes the + underlying file at the end of the context block. + + This class also supports asynchronous iteration:: + + async with await open_file(...) as f: + async for line in f: + print(line) + """ + + def __init__(self, fp: IO[AnyStr]) -> None: + self._fp: Any = fp + + def __getattr__(self, name: str) -> object: + return getattr(self._fp, name) + + @property + def wrapped(self) -> IO[AnyStr]: + """The wrapped file object.""" + return self._fp + + async def __aiter__(self) -> AsyncIterator[AnyStr]: + while True: + line = await self.readline() + if line: + yield line + else: + break + + async def aclose(self) -> None: + return await to_thread.run_sync(self._fp.close) + + async def read(self, size: int = -1) -> AnyStr: + return await to_thread.run_sync(self._fp.read, size) + + async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes: + return await to_thread.run_sync(self._fp.read1, size) + + async def readline(self) -> AnyStr: + return await to_thread.run_sync(self._fp.readline) + + async def readlines(self) -> list[AnyStr]: + return await to_thread.run_sync(self._fp.readlines) + + async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int: + return await to_thread.run_sync(self._fp.readinto, b) + + async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int: + return await to_thread.run_sync(self._fp.readinto1, b) + + @overload + async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ... + + @overload + async def write(self: AsyncFile[str], b: str) -> int: ... + + async def write(self, b: ReadableBuffer | str) -> int: + return await to_thread.run_sync(self._fp.write, b) + + @overload + async def writelines( + self: AsyncFile[bytes], lines: Iterable[ReadableBuffer] + ) -> None: ... + + @overload + async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ... + + async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None: + return await to_thread.run_sync(self._fp.writelines, lines) + + async def truncate(self, size: int | None = None) -> int: + return await to_thread.run_sync(self._fp.truncate, size) + + async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: + return await to_thread.run_sync(self._fp.seek, offset, whence) + + async def tell(self) -> int: + return await to_thread.run_sync(self._fp.tell) + + async def flush(self) -> None: + return await to_thread.run_sync(self._fp.flush) + + +@overload +async def open_file( + file: str | PathLike[str] | int, + mode: OpenBinaryMode, + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + closefd: bool = ..., + opener: Callable[[str, int], int] | None = ..., +) -> AsyncFile[bytes]: ... + + +@overload +async def open_file( + file: str | PathLike[str] | int, + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + closefd: bool = ..., + opener: Callable[[str, int], int] | None = ..., +) -> AsyncFile[str]: ... + + +async def open_file( + file: str | PathLike[str] | int, + mode: str = "r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + closefd: bool = True, + opener: Callable[[str, int], int] | None = None, +) -> AsyncFile[Any]: + """ + Open a file asynchronously. + + The arguments are exactly the same as for the builtin :func:`open`. + + :return: an asynchronous file object + + """ + fp = await to_thread.run_sync( + open, file, mode, buffering, encoding, errors, newline, closefd, opener + ) + return AsyncFile(fp) + + +def wrap_file(file: IO[AnyStr]) -> AsyncFile[AnyStr]: + """ + Wrap an existing file as an asynchronous file. + + :param file: an existing file-like object + :return: an asynchronous file object + + """ + return AsyncFile(file) + + +@dataclass(eq=False) +class _PathIterator(AsyncIterator["Path"]): + iterator: Iterator[PathLike[str]] + + async def __anext__(self) -> Path: + nextval = await to_thread.run_sync( + next, self.iterator, None, abandon_on_cancel=True + ) + if nextval is None: + raise StopAsyncIteration from None + + return Path(nextval) + + +class Path: + """ + An asynchronous version of :class:`pathlib.Path`. + + This class cannot be substituted for :class:`pathlib.Path` or + :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike` + interface. + + It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for + the deprecated :meth:`~pathlib.Path.link_to` method. + + Some methods may be unavailable or have limited functionality, based on the Python + version: + + * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later) + * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later) + * :attr:`~pathlib.Path.info` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later) + * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only + available on Python 3.13 or later) + * :meth:`~pathlib.Path.move` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later) + * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available + on Python 3.12 or later) + * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later) + + Any methods that do disk I/O need to be awaited on. These methods are: + + * :meth:`~pathlib.Path.absolute` + * :meth:`~pathlib.Path.chmod` + * :meth:`~pathlib.Path.cwd` + * :meth:`~pathlib.Path.exists` + * :meth:`~pathlib.Path.expanduser` + * :meth:`~pathlib.Path.group` + * :meth:`~pathlib.Path.hardlink_to` + * :meth:`~pathlib.Path.home` + * :meth:`~pathlib.Path.is_block_device` + * :meth:`~pathlib.Path.is_char_device` + * :meth:`~pathlib.Path.is_dir` + * :meth:`~pathlib.Path.is_fifo` + * :meth:`~pathlib.Path.is_file` + * :meth:`~pathlib.Path.is_junction` + * :meth:`~pathlib.Path.is_mount` + * :meth:`~pathlib.Path.is_socket` + * :meth:`~pathlib.Path.is_symlink` + * :meth:`~pathlib.Path.lchmod` + * :meth:`~pathlib.Path.lstat` + * :meth:`~pathlib.Path.mkdir` + * :meth:`~pathlib.Path.open` + * :meth:`~pathlib.Path.owner` + * :meth:`~pathlib.Path.read_bytes` + * :meth:`~pathlib.Path.read_text` + * :meth:`~pathlib.Path.readlink` + * :meth:`~pathlib.Path.rename` + * :meth:`~pathlib.Path.replace` + * :meth:`~pathlib.Path.resolve` + * :meth:`~pathlib.Path.rmdir` + * :meth:`~pathlib.Path.samefile` + * :meth:`~pathlib.Path.stat` + * :meth:`~pathlib.Path.symlink_to` + * :meth:`~pathlib.Path.touch` + * :meth:`~pathlib.Path.unlink` + * :meth:`~pathlib.Path.walk` + * :meth:`~pathlib.Path.write_bytes` + * :meth:`~pathlib.Path.write_text` + + Additionally, the following methods return an async iterator yielding + :class:`~.Path` objects: + + * :meth:`~pathlib.Path.glob` + * :meth:`~pathlib.Path.iterdir` + * :meth:`~pathlib.Path.rglob` + """ + + __slots__ = "_path", "__weakref__" + + __weakref__: Any + + def __init__(self, *args: str | PathLike[str]) -> None: + self._path: Final[pathlib.Path] = pathlib.Path(*args) + + def __fspath__(self) -> str: + return self._path.__fspath__() + + def __str__(self) -> str: + return self._path.__str__() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.as_posix()!r})" + + def __bytes__(self) -> bytes: + return self._path.__bytes__() + + def __hash__(self) -> int: + return self._path.__hash__() + + def __eq__(self, other: object) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__eq__(target) + + def __lt__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__lt__(target) + + def __le__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__le__(target) + + def __gt__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__gt__(target) + + def __ge__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__ge__(target) + + def __truediv__(self, other: str | PathLike[str]) -> Path: + return Path(self._path / other) + + def __rtruediv__(self, other: str | PathLike[str]) -> Path: + return Path(other) / self + + @property + def parts(self) -> tuple[str, ...]: + return self._path.parts + + @property + def drive(self) -> str: + return self._path.drive + + @property + def root(self) -> str: + return self._path.root + + @property + def anchor(self) -> str: + return self._path.anchor + + @property + def parents(self) -> Sequence[Path]: + return tuple(Path(p) for p in self._path.parents) + + @property + def parent(self) -> Path: + return Path(self._path.parent) + + @property + def name(self) -> str: + return self._path.name + + @property + def suffix(self) -> str: + return self._path.suffix + + @property + def suffixes(self) -> list[str]: + return self._path.suffixes + + @property + def stem(self) -> str: + return self._path.stem + + async def absolute(self) -> Path: + path = await to_thread.run_sync(self._path.absolute) + return Path(path) + + def as_posix(self) -> str: + return self._path.as_posix() + + def as_uri(self) -> str: + return self._path.as_uri() + + if sys.version_info >= (3, 13): + parser: ClassVar[ModuleType] = pathlib.Path.parser + + @classmethod + def from_uri(cls, uri: str) -> Path: + return Path(pathlib.Path.from_uri(uri)) + + def full_match( + self, path_pattern: str, *, case_sensitive: bool | None = None + ) -> bool: + return self._path.full_match(path_pattern, case_sensitive=case_sensitive) + + def match( + self, path_pattern: str, *, case_sensitive: bool | None = None + ) -> bool: + return self._path.match(path_pattern, case_sensitive=case_sensitive) + else: + + def match(self, path_pattern: str) -> bool: + return self._path.match(path_pattern) + + if sys.version_info >= (3, 14): + + @property + def info(self) -> Any: # TODO: add return type annotation when Typeshed gets it + return self._path.info + + async def copy( + self, + target: str | os.PathLike[str], + *, + follow_symlinks: bool = True, + dirs_exist_ok: bool = False, + preserve_metadata: bool = False, + ) -> Path: + func = partial( + self._path.copy, + follow_symlinks=follow_symlinks, + dirs_exist_ok=dirs_exist_ok, + preserve_metadata=preserve_metadata, + ) + return Path(await to_thread.run_sync(func, target)) + + async def copy_into( + self, + target_dir: str | os.PathLike[str], + *, + follow_symlinks: bool = True, + dirs_exist_ok: bool = False, + preserve_metadata: bool = False, + ) -> Path: + func = partial( + self._path.copy_into, + follow_symlinks=follow_symlinks, + dirs_exist_ok=dirs_exist_ok, + preserve_metadata=preserve_metadata, + ) + return Path(await to_thread.run_sync(func, target_dir)) + + async def move(self, target: str | os.PathLike[str]) -> Path: + # Upstream does not handle anyio.Path properly as a PathLike + target = pathlib.Path(target) + return Path(await to_thread.run_sync(self._path.move, target)) + + async def move_into( + self, + target_dir: str | os.PathLike[str], + ) -> Path: + return Path(await to_thread.run_sync(self._path.move_into, target_dir)) + + def is_relative_to(self, other: str | PathLike[str]) -> bool: + try: + self.relative_to(other) + return True + except ValueError: + return False + + async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None: + func = partial(os.chmod, follow_symlinks=follow_symlinks) + return await to_thread.run_sync(func, self._path, mode) + + @classmethod + async def cwd(cls) -> Path: + path = await to_thread.run_sync(pathlib.Path.cwd) + return cls(path) + + async def exists(self) -> bool: + return await to_thread.run_sync(self._path.exists, abandon_on_cancel=True) + + async def expanduser(self) -> Path: + return Path( + await to_thread.run_sync(self._path.expanduser, abandon_on_cancel=True) + ) + + def glob(self, pattern: str) -> AsyncIterator[Path]: + gen = self._path.glob(pattern) + return _PathIterator(gen) + + async def group(self) -> str: + return await to_thread.run_sync(self._path.group, abandon_on_cancel=True) + + async def hardlink_to( + self, target: str | bytes | PathLike[str] | PathLike[bytes] + ) -> None: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(os.link, target, self) + + @classmethod + async def home(cls) -> Path: + home_path = await to_thread.run_sync(pathlib.Path.home) + return cls(home_path) + + def is_absolute(self) -> bool: + return self._path.is_absolute() + + async def is_block_device(self) -> bool: + return await to_thread.run_sync( + self._path.is_block_device, abandon_on_cancel=True + ) + + async def is_char_device(self) -> bool: + return await to_thread.run_sync( + self._path.is_char_device, abandon_on_cancel=True + ) + + async def is_dir(self) -> bool: + return await to_thread.run_sync(self._path.is_dir, abandon_on_cancel=True) + + async def is_fifo(self) -> bool: + return await to_thread.run_sync(self._path.is_fifo, abandon_on_cancel=True) + + async def is_file(self) -> bool: + return await to_thread.run_sync(self._path.is_file, abandon_on_cancel=True) + + if sys.version_info >= (3, 12): + + async def is_junction(self) -> bool: + return await to_thread.run_sync(self._path.is_junction) + + async def is_mount(self) -> bool: + return await to_thread.run_sync( + os.path.ismount, self._path, abandon_on_cancel=True + ) + + def is_reserved(self) -> bool: + return self._path.is_reserved() + + async def is_socket(self) -> bool: + return await to_thread.run_sync(self._path.is_socket, abandon_on_cancel=True) + + async def is_symlink(self) -> bool: + return await to_thread.run_sync(self._path.is_symlink, abandon_on_cancel=True) + + async def iterdir(self) -> AsyncIterator[Path]: + gen = ( + self._path.iterdir() + if sys.version_info < (3, 13) + else await to_thread.run_sync(self._path.iterdir, abandon_on_cancel=True) + ) + async for path in _PathIterator(gen): + yield path + + def joinpath(self, *args: str | PathLike[str]) -> Path: + return Path(self._path.joinpath(*args)) + + async def lchmod(self, mode: int) -> None: + await to_thread.run_sync(self._path.lchmod, mode) + + async def lstat(self) -> os.stat_result: + return await to_thread.run_sync(self._path.lstat, abandon_on_cancel=True) + + async def mkdir( + self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False + ) -> None: + await to_thread.run_sync(self._path.mkdir, mode, parents, exist_ok) + + @overload + async def open( + self, + mode: OpenBinaryMode, + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + ) -> AsyncFile[bytes]: ... + + @overload + async def open( + self, + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + ) -> AsyncFile[str]: ... + + async def open( + self, + mode: str = "r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> AsyncFile[Any]: + fp = await to_thread.run_sync( + self._path.open, mode, buffering, encoding, errors, newline + ) + return AsyncFile(fp) + + async def owner(self) -> str: + return await to_thread.run_sync(self._path.owner, abandon_on_cancel=True) + + async def read_bytes(self) -> bytes: + return await to_thread.run_sync(self._path.read_bytes) + + async def read_text( + self, encoding: str | None = None, errors: str | None = None + ) -> str: + return await to_thread.run_sync(self._path.read_text, encoding, errors) + + if sys.version_info >= (3, 12): + + def relative_to( + self, *other: str | PathLike[str], walk_up: bool = False + ) -> Path: + return Path(self._path.relative_to(*other, walk_up=walk_up)) + + else: + + def relative_to(self, *other: str | PathLike[str]) -> Path: + return Path(self._path.relative_to(*other)) + + async def readlink(self) -> Path: + target = await to_thread.run_sync(os.readlink, self._path) + return Path(target) + + async def rename(self, target: str | pathlib.PurePath | Path) -> Path: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.rename, target) + return Path(target) + + async def replace(self, target: str | pathlib.PurePath | Path) -> Path: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.replace, target) + return Path(target) + + async def resolve(self, strict: bool = False) -> Path: + func = partial(self._path.resolve, strict=strict) + return Path(await to_thread.run_sync(func, abandon_on_cancel=True)) + + def rglob(self, pattern: str) -> AsyncIterator[Path]: + gen = self._path.rglob(pattern) + return _PathIterator(gen) + + async def rmdir(self) -> None: + await to_thread.run_sync(self._path.rmdir) + + async def samefile(self, other_path: str | PathLike[str]) -> bool: + if isinstance(other_path, Path): + other_path = other_path._path + + return await to_thread.run_sync( + self._path.samefile, other_path, abandon_on_cancel=True + ) + + async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result: + func = partial(os.stat, follow_symlinks=follow_symlinks) + return await to_thread.run_sync(func, self._path, abandon_on_cancel=True) + + async def symlink_to( + self, + target: str | bytes | PathLike[str] | PathLike[bytes], + target_is_directory: bool = False, + ) -> None: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.symlink_to, target, target_is_directory) + + async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: + await to_thread.run_sync(self._path.touch, mode, exist_ok) + + async def unlink(self, missing_ok: bool = False) -> None: + try: + await to_thread.run_sync(self._path.unlink) + except FileNotFoundError: + if not missing_ok: + raise + + if sys.version_info >= (3, 12): + + async def walk( + self, + top_down: bool = True, + on_error: Callable[[OSError], object] | None = None, + follow_symlinks: bool = False, + ) -> AsyncIterator[tuple[Path, list[str], list[str]]]: + def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None: + try: + return next(gen) + except StopIteration: + return None + + gen = self._path.walk(top_down, on_error, follow_symlinks) + while True: + value = await to_thread.run_sync(get_next_value) + if value is None: + return + + root, dirs, paths = value + yield Path(root), dirs, paths + + def with_name(self, name: str) -> Path: + return Path(self._path.with_name(name)) + + def with_stem(self, stem: str) -> Path: + return Path(self._path.with_name(stem + self._path.suffix)) + + def with_suffix(self, suffix: str) -> Path: + return Path(self._path.with_suffix(suffix)) + + def with_segments(self, *pathsegments: str | PathLike[str]) -> Path: + return Path(*pathsegments) + + async def write_bytes(self, data: bytes) -> int: + return await to_thread.run_sync(self._path.write_bytes, data) + + async def write_text( + self, + data: str, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> int: + # Path.write_text() does not support the "newline" parameter before Python 3.10 + def sync_write_text() -> int: + with self._path.open( + "w", encoding=encoding, errors=errors, newline=newline + ) as fp: + return fp.write(data) + + return await to_thread.run_sync(sync_write_text) + + +PathLike.register(Path) diff --git a/.venv/Lib/site-packages/anyio/_core/_resources.py b/.venv/Lib/site-packages/anyio/_core/_resources.py new file mode 100644 index 0000000..b9a5344 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_resources.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from ..abc import AsyncResource +from ._tasks import CancelScope + + +async def aclose_forcefully(resource: AsyncResource) -> None: + """ + Close an asynchronous resource in a cancelled scope. + + Doing this closes the resource without waiting on anything. + + :param resource: the resource to close + + """ + with CancelScope() as scope: + scope.cancel() + await resource.aclose() diff --git a/.venv/Lib/site-packages/anyio/_core/_signals.py b/.venv/Lib/site-packages/anyio/_core/_signals.py new file mode 100644 index 0000000..f3451d3 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_signals.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from collections.abc import AsyncIterator +from contextlib import AbstractContextManager +from signal import Signals + +from ._eventloop import get_async_backend + + +def open_signal_receiver( + *signals: Signals, +) -> AbstractContextManager[AsyncIterator[Signals]]: + """ + Start receiving operating system signals. + + :param signals: signals to receive (e.g. ``signal.SIGINT``) + :return: an asynchronous context manager for an asynchronous iterator which yields + signal numbers + + .. warning:: Windows does not support signals natively so it is best to avoid + relying on this in cross-platform applications. + + .. warning:: On asyncio, this permanently replaces any previous signal handler for + the given signals, as set via :meth:`~asyncio.loop.add_signal_handler`. + + """ + return get_async_backend().open_signal_receiver(*signals) diff --git a/.venv/Lib/site-packages/anyio/_core/_sockets.py b/.venv/Lib/site-packages/anyio/_core/_sockets.py new file mode 100644 index 0000000..054bcdd --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_sockets.py @@ -0,0 +1,792 @@ +from __future__ import annotations + +import errno +import os +import socket +import ssl +import stat +import sys +from collections.abc import Awaitable +from ipaddress import IPv6Address, ip_address +from os import PathLike, chmod +from socket import AddressFamily, SocketKind +from typing import TYPE_CHECKING, Any, Literal, cast, overload + +from .. import to_thread +from ..abc import ( + ConnectedUDPSocket, + ConnectedUNIXDatagramSocket, + IPAddressType, + IPSockAddrType, + SocketListener, + SocketStream, + UDPSocket, + UNIXDatagramSocket, + UNIXSocketStream, +) +from ..streams.stapled import MultiListener +from ..streams.tls import TLSStream +from ._eventloop import get_async_backend +from ._resources import aclose_forcefully +from ._synchronization import Event +from ._tasks import create_task_group, move_on_after + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike +else: + FileDescriptorLike = object + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + +if sys.version_info < (3, 13): + from typing_extensions import deprecated +else: + from warnings import deprecated + +IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) # https://bugs.python.org/issue29515 + +AnyIPAddressFamily = Literal[ + AddressFamily.AF_UNSPEC, AddressFamily.AF_INET, AddressFamily.AF_INET6 +] +IPAddressFamily = Literal[AddressFamily.AF_INET, AddressFamily.AF_INET6] + + +# tls_hostname given +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str, + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# ssl_context given +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + ssl_context: ssl.SSLContext, + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# tls=True +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + tls: Literal[True], + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# tls=False +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + tls: Literal[False], + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> SocketStream: ... + + +# No TLS arguments +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + happy_eyeballs_delay: float = ..., +) -> SocketStream: ... + + +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = None, + tls: bool = False, + ssl_context: ssl.SSLContext | None = None, + tls_standard_compatible: bool = True, + tls_hostname: str | None = None, + happy_eyeballs_delay: float = 0.25, +) -> SocketStream | TLSStream: + """ + Connect to a host using the TCP protocol. + + This function implements the stateless version of the Happy Eyeballs algorithm (RFC + 6555). If ``remote_host`` is a host name that resolves to multiple IP addresses, + each one is tried until one connection attempt succeeds. If the first attempt does + not connected within 250 milliseconds, a second attempt is started using the next + address in the list, and so on. On IPv6 enabled systems, an IPv6 address (if + available) is tried first. + + When the connection has been established, a TLS handshake will be done if either + ``ssl_context`` or ``tls_hostname`` is not ``None``, or if ``tls`` is ``True``. + + :param remote_host: the IP address or host name to connect to + :param remote_port: port on the target host to connect to + :param local_host: the interface address or name to bind the socket to before + connecting + :param tls: ``True`` to do a TLS handshake with the connected stream and return a + :class:`~anyio.streams.tls.TLSStream` instead + :param ssl_context: the SSL context object to use (if omitted, a default context is + created) + :param tls_standard_compatible: If ``True``, performs the TLS shutdown handshake + before closing the stream and requires that the server does this as well. + Otherwise, :exc:`~ssl.SSLEOFError` may be raised during reads from the stream. + Some protocols, such as HTTP, require this option to be ``False``. + See :meth:`~ssl.SSLContext.wrap_socket` for details. + :param tls_hostname: host name to check the server certificate against (defaults to + the value of ``remote_host``) + :param happy_eyeballs_delay: delay (in seconds) before starting the next connection + attempt + :return: a socket stream object if no TLS handshake was done, otherwise a TLS stream + :raises OSError: if the connection attempt fails + + """ + # Placed here due to https://github.com/python/mypy/issues/7057 + connected_stream: SocketStream | None = None + + async def try_connect(remote_host: str, event: Event) -> None: + nonlocal connected_stream + try: + stream = await asynclib.connect_tcp(remote_host, remote_port, local_address) + except OSError as exc: + oserrors.append(exc) + return + else: + if connected_stream is None: + connected_stream = stream + tg.cancel_scope.cancel() + else: + await stream.aclose() + finally: + event.set() + + asynclib = get_async_backend() + local_address: IPSockAddrType | None = None + family = socket.AF_UNSPEC + if local_host: + gai_res = await getaddrinfo(str(local_host), None) + family, *_, local_address = gai_res[0] + + target_host = str(remote_host) + try: + addr_obj = ip_address(remote_host) + except ValueError: + addr_obj = None + + if addr_obj is not None: + if isinstance(addr_obj, IPv6Address): + target_addrs = [(socket.AF_INET6, addr_obj.compressed)] + else: + target_addrs = [(socket.AF_INET, addr_obj.compressed)] + else: + # getaddrinfo() will raise an exception if name resolution fails + gai_res = await getaddrinfo( + target_host, remote_port, family=family, type=socket.SOCK_STREAM + ) + + # Organize the list so that the first address is an IPv6 address (if available) + # and the second one is an IPv4 addresses. The rest can be in whatever order. + v6_found = v4_found = False + target_addrs = [] + for af, *rest, sa in gai_res: + if af == socket.AF_INET6 and not v6_found: + v6_found = True + target_addrs.insert(0, (af, sa[0])) + elif af == socket.AF_INET and not v4_found and v6_found: + v4_found = True + target_addrs.insert(1, (af, sa[0])) + else: + target_addrs.append((af, sa[0])) + + oserrors: list[OSError] = [] + try: + async with create_task_group() as tg: + for i, (af, addr) in enumerate(target_addrs): + event = Event() + tg.start_soon(try_connect, addr, event) + with move_on_after(happy_eyeballs_delay): + await event.wait() + + if connected_stream is None: + cause = ( + oserrors[0] + if len(oserrors) == 1 + else ExceptionGroup("multiple connection attempts failed", oserrors) + ) + raise OSError("All connection attempts failed") from cause + finally: + oserrors.clear() + + if tls or tls_hostname or ssl_context: + try: + return await TLSStream.wrap( + connected_stream, + server_side=False, + hostname=tls_hostname or str(remote_host), + ssl_context=ssl_context, + standard_compatible=tls_standard_compatible, + ) + except BaseException: + await aclose_forcefully(connected_stream) + raise + + return connected_stream + + +async def connect_unix(path: str | bytes | PathLike[Any]) -> UNIXSocketStream: + """ + Connect to the given UNIX socket. + + Not available on Windows. + + :param path: path to the socket + :return: a socket stream object + + """ + path = os.fspath(path) + return await get_async_backend().connect_unix(path) + + +async def create_tcp_listener( + *, + local_host: IPAddressType | None = None, + local_port: int = 0, + family: AnyIPAddressFamily = socket.AddressFamily.AF_UNSPEC, + backlog: int = 65536, + reuse_port: bool = False, +) -> MultiListener[SocketStream]: + """ + Create a TCP socket listener. + + :param local_port: port number to listen on + :param local_host: IP address of the interface to listen on. If omitted, listen on + all IPv4 and IPv6 interfaces. To listen on all interfaces on a specific address + family, use ``0.0.0.0`` for IPv4 or ``::`` for IPv6. + :param family: address family (used if ``local_host`` was omitted) + :param backlog: maximum number of queued incoming connections (up to a maximum of + 2**16, or 65536) + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a list of listener objects + + """ + asynclib = get_async_backend() + backlog = min(backlog, 65536) + local_host = str(local_host) if local_host is not None else None + gai_res = await getaddrinfo( + local_host, + local_port, + family=family, + type=socket.SocketKind.SOCK_STREAM if sys.platform == "win32" else 0, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + listeners: list[SocketListener] = [] + try: + # The set() is here to work around a glibc bug: + # https://sourceware.org/bugzilla/show_bug.cgi?id=14969 + sockaddr: tuple[str, int] | tuple[str, int, int, int] + for fam, kind, *_, sockaddr in sorted(set(gai_res)): + # Workaround for an uvloop bug where we don't get the correct scope ID for + # IPv6 link-local addresses when passing type=socket.SOCK_STREAM to + # getaddrinfo(): https://github.com/MagicStack/uvloop/issues/539 + if sys.platform != "win32" and kind is not SocketKind.SOCK_STREAM: + continue + + raw_socket = socket.socket(fam) + raw_socket.setblocking(False) + + # For Windows, enable exclusive address use. For others, enable address + # reuse. + if sys.platform == "win32": + raw_socket.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + else: + raw_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + if reuse_port: + raw_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + # If only IPv6 was requested, disable dual stack operation + if fam == socket.AF_INET6: + raw_socket.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) + + # Workaround for #554 + if "%" in sockaddr[0]: + addr, scope_id = sockaddr[0].split("%", 1) + sockaddr = (addr, sockaddr[1], 0, int(scope_id)) + + raw_socket.bind(sockaddr) + raw_socket.listen(backlog) + listener = asynclib.create_tcp_listener(raw_socket) + listeners.append(listener) + except BaseException: + for listener in listeners: + await listener.aclose() + + raise + + return MultiListener(listeners) + + +async def create_unix_listener( + path: str | bytes | PathLike[Any], + *, + mode: int | None = None, + backlog: int = 65536, +) -> SocketListener: + """ + Create a UNIX socket listener. + + Not available on Windows. + + :param path: path of the socket + :param mode: permissions to set on the socket + :param backlog: maximum number of queued incoming connections (up to a maximum of + 2**16, or 65536) + :return: a listener object + + .. versionchanged:: 3.0 + If a socket already exists on the file system in the given path, it will be + removed first. + + """ + backlog = min(backlog, 65536) + raw_socket = await setup_unix_local_socket(path, mode, socket.SOCK_STREAM) + try: + raw_socket.listen(backlog) + return get_async_backend().create_unix_listener(raw_socket) + except BaseException: + raw_socket.close() + raise + + +async def create_udp_socket( + family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, + *, + local_host: IPAddressType | None = None, + local_port: int = 0, + reuse_port: bool = False, +) -> UDPSocket: + """ + Create a UDP socket. + + If ``port`` has been given, the socket will be bound to this port on the local + machine, making this socket suitable for providing UDP based services. + + :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically + determined from ``local_host`` if omitted + :param local_host: IP address or host name of the local interface to bind to + :param local_port: local port to bind to + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a UDP socket + + """ + if family is AddressFamily.AF_UNSPEC and not local_host: + raise ValueError('Either "family" or "local_host" must be given') + + if local_host: + gai_res = await getaddrinfo( + str(local_host), + local_port, + family=family, + type=socket.SOCK_DGRAM, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + local_address = gai_res[0][-1] + elif family is AddressFamily.AF_INET6: + local_address = ("::", 0) + else: + local_address = ("0.0.0.0", 0) + + sock = await get_async_backend().create_udp_socket( + family, local_address, None, reuse_port + ) + return cast(UDPSocket, sock) + + +async def create_connected_udp_socket( + remote_host: IPAddressType, + remote_port: int, + *, + family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, + local_host: IPAddressType | None = None, + local_port: int = 0, + reuse_port: bool = False, +) -> ConnectedUDPSocket: + """ + Create a connected UDP socket. + + Connected UDP sockets can only communicate with the specified remote host/port, an + any packets sent from other sources are dropped. + + :param remote_host: remote host to set as the default target + :param remote_port: port on the remote host to set as the default target + :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically + determined from ``local_host`` or ``remote_host`` if omitted + :param local_host: IP address or host name of the local interface to bind to + :param local_port: local port to bind to + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a connected UDP socket + + """ + local_address = None + if local_host: + gai_res = await getaddrinfo( + str(local_host), + local_port, + family=family, + type=socket.SOCK_DGRAM, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + local_address = gai_res[0][-1] + + gai_res = await getaddrinfo( + str(remote_host), remote_port, family=family, type=socket.SOCK_DGRAM + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + remote_address = gai_res[0][-1] + + sock = await get_async_backend().create_udp_socket( + family, local_address, remote_address, reuse_port + ) + return cast(ConnectedUDPSocket, sock) + + +async def create_unix_datagram_socket( + *, + local_path: None | str | bytes | PathLike[Any] = None, + local_mode: int | None = None, +) -> UNIXDatagramSocket: + """ + Create a UNIX datagram socket. + + Not available on Windows. + + If ``local_path`` has been given, the socket will be bound to this path, making this + socket suitable for receiving datagrams from other processes. Other processes can + send datagrams to this socket only if ``local_path`` is set. + + If a socket already exists on the file system in the ``local_path``, it will be + removed first. + + :param local_path: the path on which to bind to + :param local_mode: permissions to set on the local socket + :return: a UNIX datagram socket + + """ + raw_socket = await setup_unix_local_socket( + local_path, local_mode, socket.SOCK_DGRAM + ) + return await get_async_backend().create_unix_datagram_socket(raw_socket, None) + + +async def create_connected_unix_datagram_socket( + remote_path: str | bytes | PathLike[Any], + *, + local_path: None | str | bytes | PathLike[Any] = None, + local_mode: int | None = None, +) -> ConnectedUNIXDatagramSocket: + """ + Create a connected UNIX datagram socket. + + Connected datagram sockets can only communicate with the specified remote path. + + If ``local_path`` has been given, the socket will be bound to this path, making + this socket suitable for receiving datagrams from other processes. Other processes + can send datagrams to this socket only if ``local_path`` is set. + + If a socket already exists on the file system in the ``local_path``, it will be + removed first. + + :param remote_path: the path to set as the default target + :param local_path: the path on which to bind to + :param local_mode: permissions to set on the local socket + :return: a connected UNIX datagram socket + + """ + remote_path = os.fspath(remote_path) + raw_socket = await setup_unix_local_socket( + local_path, local_mode, socket.SOCK_DGRAM + ) + return await get_async_backend().create_unix_datagram_socket( + raw_socket, remote_path + ) + + +async def getaddrinfo( + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, +) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int]]]: + """ + Look up a numeric IP address given a host name. + + Internationalized domain names are translated according to the (non-transitional) + IDNA 2008 standard. + + .. note:: 4-tuple IPv6 socket addresses are automatically converted to 2-tuples of + (host, port), unlike what :func:`socket.getaddrinfo` does. + + :param host: host name + :param port: port number + :param family: socket family (`'AF_INET``, ...) + :param type: socket type (``SOCK_STREAM``, ...) + :param proto: protocol number + :param flags: flags to pass to upstream ``getaddrinfo()`` + :return: list of tuples containing (family, type, proto, canonname, sockaddr) + + .. seealso:: :func:`socket.getaddrinfo` + + """ + # Handle unicode hostnames + if isinstance(host, str): + try: + encoded_host: bytes | None = host.encode("ascii") + except UnicodeEncodeError: + import idna + + encoded_host = idna.encode(host, uts46=True) + else: + encoded_host = host + + gai_res = await get_async_backend().getaddrinfo( + encoded_host, port, family=family, type=type, proto=proto, flags=flags + ) + return [ + (family, type, proto, canonname, convert_ipv6_sockaddr(sockaddr)) + for family, type, proto, canonname, sockaddr in gai_res + # filter out IPv6 results when IPv6 is disabled + if not isinstance(sockaddr[0], int) + ] + + +def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str, str]]: + """ + Look up the host name of an IP address. + + :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4) + :param flags: flags to pass to upstream ``getnameinfo()`` + :return: a tuple of (host name, service name) + + .. seealso:: :func:`socket.getnameinfo` + + """ + return get_async_backend().getnameinfo(sockaddr, flags) + + +@deprecated("This function is deprecated; use `wait_readable` instead") +def wait_socket_readable(sock: socket.socket) -> Awaitable[None]: + """ + .. deprecated:: 4.7.0 + Use :func:`wait_readable` instead. + + Wait until the given socket has data to be read. + + .. warning:: Only use this on raw sockets that have not been wrapped by any higher + level constructs like socket streams! + + :param sock: a socket object + :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the + socket to become readable + :raises ~anyio.BusyResourceError: if another task is already waiting for the socket + to become readable + + """ + return get_async_backend().wait_readable(sock.fileno()) + + +@deprecated("This function is deprecated; use `wait_writable` instead") +def wait_socket_writable(sock: socket.socket) -> Awaitable[None]: + """ + .. deprecated:: 4.7.0 + Use :func:`wait_writable` instead. + + Wait until the given socket can be written to. + + This does **NOT** work on Windows when using the asyncio backend with a proactor + event loop (default on py3.8+). + + .. warning:: Only use this on raw sockets that have not been wrapped by any higher + level constructs like socket streams! + + :param sock: a socket object + :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the + socket to become writable + :raises ~anyio.BusyResourceError: if another task is already waiting for the socket + to become writable + + """ + return get_async_backend().wait_writable(sock.fileno()) + + +def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]: + """ + Wait until the given object has data to be read. + + On Unix systems, ``obj`` must either be an integer file descriptor, or else an + object with a ``.fileno()`` method which returns an integer file descriptor. Any + kind of file descriptor can be passed, though the exact semantics will depend on + your kernel. For example, this probably won't do anything useful for on-disk files. + + On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an + object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File + descriptors aren't supported, and neither are handles that refer to anything besides + a ``SOCKET``. + + On backends where this functionality is not natively provided (asyncio + ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread + which is set to shut down when the interpreter shuts down. + + .. warning:: Don't use this on raw sockets that have been wrapped by any higher + level constructs like socket streams! + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the + object to become readable + :raises ~anyio.BusyResourceError: if another task is already waiting for the object + to become readable + + """ + return get_async_backend().wait_readable(obj) + + +def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]: + """ + Wait until the given object can be written to. + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the + object to become writable + :raises ~anyio.BusyResourceError: if another task is already waiting for the object + to become writable + + .. seealso:: See the documentation of :func:`wait_readable` for the definition of + ``obj`` and notes on backend compatibility. + + .. warning:: Don't use this on raw sockets that have been wrapped by any higher + level constructs like socket streams! + + """ + return get_async_backend().wait_writable(obj) + + +# +# Private API +# + + +def convert_ipv6_sockaddr( + sockaddr: tuple[str, int, int, int] | tuple[str, int], +) -> tuple[str, int]: + """ + Convert a 4-tuple IPv6 socket address to a 2-tuple (address, port) format. + + If the scope ID is nonzero, it is added to the address, separated with ``%``. + Otherwise the flow id and scope id are simply cut off from the tuple. + Any other kinds of socket addresses are returned as-is. + + :param sockaddr: the result of :meth:`~socket.socket.getsockname` + :return: the converted socket address + + """ + # This is more complicated than it should be because of MyPy + if isinstance(sockaddr, tuple) and len(sockaddr) == 4: + host, port, flowinfo, scope_id = sockaddr + if scope_id: + # PyPy (as of v7.3.11) leaves the interface name in the result, so + # we discard it and only get the scope ID from the end + # (https://foss.heptapod.net/pypy/pypy/-/issues/3938) + host = host.split("%")[0] + + # Add scope_id to the address + return f"{host}%{scope_id}", port + else: + return host, port + else: + return sockaddr + + +async def setup_unix_local_socket( + path: None | str | bytes | PathLike[Any], + mode: int | None, + socktype: int, +) -> socket.socket: + """ + Create a UNIX local socket object, deleting the socket at the given path if it + exists. + + Not available on Windows. + + :param path: path of the socket + :param mode: permissions to set on the socket + :param socktype: socket.SOCK_STREAM or socket.SOCK_DGRAM + + """ + path_str: str | None + if path is not None: + path_str = os.fsdecode(path) + + # Linux abstract namespace sockets aren't backed by a concrete file so skip stat call + if not path_str.startswith("\0"): + # Copied from pathlib... + try: + stat_result = os.stat(path) + except OSError as e: + if e.errno not in ( + errno.ENOENT, + errno.ENOTDIR, + errno.EBADF, + errno.ELOOP, + ): + raise + else: + if stat.S_ISSOCK(stat_result.st_mode): + os.unlink(path) + else: + path_str = None + + raw_socket = socket.socket(socket.AF_UNIX, socktype) + raw_socket.setblocking(False) + + if path_str is not None: + try: + await to_thread.run_sync(raw_socket.bind, path_str, abandon_on_cancel=True) + if mode is not None: + await to_thread.run_sync(chmod, path_str, mode, abandon_on_cancel=True) + except BaseException: + raw_socket.close() + raise + + return raw_socket diff --git a/.venv/Lib/site-packages/anyio/_core/_streams.py b/.venv/Lib/site-packages/anyio/_core/_streams.py new file mode 100644 index 0000000..6a9814e --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_streams.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import math +from typing import TypeVar +from warnings import warn + +from ..streams.memory import ( + MemoryObjectReceiveStream, + MemoryObjectSendStream, + MemoryObjectStreamState, +) + +T_Item = TypeVar("T_Item") + + +class create_memory_object_stream( + tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]], +): + """ + Create a memory object stream. + + The stream's item type can be annotated like + :func:`create_memory_object_stream[T_Item]`. + + :param max_buffer_size: number of items held in the buffer until ``send()`` starts + blocking + :param item_type: old way of marking the streams with the right generic type for + static typing (does nothing on AnyIO 4) + + .. deprecated:: 4.0 + Use ``create_memory_object_stream[YourItemType](...)`` instead. + :return: a tuple of (send stream, receive stream) + + """ + + def __new__( # type: ignore[misc] + cls, max_buffer_size: float = 0, item_type: object = None + ) -> tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]]: + if max_buffer_size != math.inf and not isinstance(max_buffer_size, int): + raise ValueError("max_buffer_size must be either an integer or math.inf") + if max_buffer_size < 0: + raise ValueError("max_buffer_size cannot be negative") + if item_type is not None: + warn( + "The item_type argument has been deprecated in AnyIO 4.0. " + "Use create_memory_object_stream[YourItemType](...) instead.", + DeprecationWarning, + stacklevel=2, + ) + + state = MemoryObjectStreamState[T_Item](max_buffer_size) + return (MemoryObjectSendStream(state), MemoryObjectReceiveStream(state)) diff --git a/.venv/Lib/site-packages/anyio/_core/_subprocesses.py b/.venv/Lib/site-packages/anyio/_core/_subprocesses.py new file mode 100644 index 0000000..36d9b30 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_subprocesses.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +import sys +from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from io import BytesIO +from os import PathLike +from subprocess import PIPE, CalledProcessError, CompletedProcess +from typing import IO, Any, Union, cast + +from ..abc import Process +from ._eventloop import get_async_backend +from ._tasks import create_task_group + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + +StrOrBytesPath: TypeAlias = Union[str, bytes, "PathLike[str]", "PathLike[bytes]"] + + +async def run_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + input: bytes | None = None, + stdin: int | IO[Any] | None = None, + stdout: int | IO[Any] | None = PIPE, + stderr: int | IO[Any] | None = PIPE, + check: bool = True, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, +) -> CompletedProcess[bytes]: + """ + Run an external command in a subprocess and wait until it completes. + + .. seealso:: :func:`subprocess.run` + + :param command: either a string to pass to the shell, or an iterable of strings + containing the executable name or path and its arguments + :param input: bytes passed to the standard input of the subprocess + :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or `None`; ``input`` overrides this + :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or `None` + :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + :data:`subprocess.STDOUT`, a file-like object, or `None` + :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the + process terminates with a return code other than 0 + :param cwd: If not ``None``, change the working directory to this before running the + command + :param env: if not ``None``, this mapping replaces the inherited environment + variables from the parent process + :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used + to specify process startup parameters (Windows only) + :param creationflags: flags that can be used to control the creation of the + subprocess (see :class:`subprocess.Popen` for the specifics) + :param start_new_session: if ``true`` the setsid() system call will be made in the + child process prior to the execution of the subprocess. (POSIX only) + :param pass_fds: sequence of file descriptors to keep open between the parent and + child processes. (POSIX only) + :param user: effective user to run the process as (Python >= 3.9, POSIX only) + :param group: effective group to run the process as (Python >= 3.9, POSIX only) + :param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9, + POSIX only) + :param umask: if not negative, this umask is applied in the child process before + running the given command (Python >= 3.9, POSIX only) + :return: an object representing the completed process + :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process + exits with a nonzero return code + + """ + + async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None: + buffer = BytesIO() + async for chunk in stream: + buffer.write(chunk) + + stream_contents[index] = buffer.getvalue() + + if stdin is not None and input is not None: + raise ValueError("only one of stdin and input is allowed") + + async with await open_process( + command, + stdin=PIPE if input else stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd, + env=env, + startupinfo=startupinfo, + creationflags=creationflags, + start_new_session=start_new_session, + pass_fds=pass_fds, + user=user, + group=group, + extra_groups=extra_groups, + umask=umask, + ) as process: + stream_contents: list[bytes | None] = [None, None] + async with create_task_group() as tg: + if process.stdout: + tg.start_soon(drain_stream, process.stdout, 0) + + if process.stderr: + tg.start_soon(drain_stream, process.stderr, 1) + + if process.stdin and input: + await process.stdin.send(input) + await process.stdin.aclose() + + await process.wait() + + output, errors = stream_contents + if check and process.returncode != 0: + raise CalledProcessError(cast(int, process.returncode), command, output, errors) + + return CompletedProcess(command, cast(int, process.returncode), output, errors) + + +async def open_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None = PIPE, + stdout: int | IO[Any] | None = PIPE, + stderr: int | IO[Any] | None = PIPE, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, +) -> Process: + """ + Start an external command in a subprocess. + + .. seealso:: :class:`subprocess.Popen` + + :param command: either a string to pass to the shell, or an iterable of strings + containing the executable name or path and its arguments + :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a + file-like object, or ``None`` + :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or ``None`` + :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + :data:`subprocess.STDOUT`, a file-like object, or ``None`` + :param cwd: If not ``None``, the working directory is changed before executing + :param env: If env is not ``None``, it must be a mapping that defines the + environment variables for the new process + :param creationflags: flags that can be used to control the creation of the + subprocess (see :class:`subprocess.Popen` for the specifics) + :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used + to specify process startup parameters (Windows only) + :param start_new_session: if ``true`` the setsid() system call will be made in the + child process prior to the execution of the subprocess. (POSIX only) + :param pass_fds: sequence of file descriptors to keep open between the parent and + child processes. (POSIX only) + :param user: effective user to run the process as (POSIX only) + :param group: effective group to run the process as (POSIX only) + :param extra_groups: supplementary groups to set in the subprocess (POSIX only) + :param umask: if not negative, this umask is applied in the child process before + running the given command (POSIX only) + :return: an asynchronous process object + + """ + kwargs: dict[str, Any] = {} + if user is not None: + kwargs["user"] = user + + if group is not None: + kwargs["group"] = group + + if extra_groups is not None: + kwargs["extra_groups"] = group + + if umask >= 0: + kwargs["umask"] = umask + + return await get_async_backend().open_process( + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd, + env=env, + startupinfo=startupinfo, + creationflags=creationflags, + start_new_session=start_new_session, + pass_fds=pass_fds, + **kwargs, + ) diff --git a/.venv/Lib/site-packages/anyio/_core/_synchronization.py b/.venv/Lib/site-packages/anyio/_core/_synchronization.py new file mode 100644 index 0000000..a633132 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_synchronization.py @@ -0,0 +1,732 @@ +from __future__ import annotations + +import math +from collections import deque +from dataclasses import dataclass +from types import TracebackType + +from sniffio import AsyncLibraryNotFoundError + +from ..lowlevel import checkpoint +from ._eventloop import get_async_backend +from ._exceptions import BusyResourceError +from ._tasks import CancelScope +from ._testing import TaskInfo, get_current_task + + +@dataclass(frozen=True) +class EventStatistics: + """ + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Event.wait` + """ + + tasks_waiting: int + + +@dataclass(frozen=True) +class CapacityLimiterStatistics: + """ + :ivar int borrowed_tokens: number of tokens currently borrowed by tasks + :ivar float total_tokens: total number of available tokens + :ivar tuple borrowers: tasks or other objects currently holding tokens borrowed from + this limiter + :ivar int tasks_waiting: number of tasks waiting on + :meth:`~.CapacityLimiter.acquire` or + :meth:`~.CapacityLimiter.acquire_on_behalf_of` + """ + + borrowed_tokens: int + total_tokens: float + borrowers: tuple[object, ...] + tasks_waiting: int + + +@dataclass(frozen=True) +class LockStatistics: + """ + :ivar bool locked: flag indicating if this lock is locked or not + :ivar ~anyio.TaskInfo owner: task currently holding the lock (or ``None`` if the + lock is not held by any task) + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Lock.acquire` + """ + + locked: bool + owner: TaskInfo | None + tasks_waiting: int + + +@dataclass(frozen=True) +class ConditionStatistics: + """ + :ivar int tasks_waiting: number of tasks blocked on :meth:`~.Condition.wait` + :ivar ~anyio.LockStatistics lock_statistics: statistics of the underlying + :class:`~.Lock` + """ + + tasks_waiting: int + lock_statistics: LockStatistics + + +@dataclass(frozen=True) +class SemaphoreStatistics: + """ + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Semaphore.acquire` + + """ + + tasks_waiting: int + + +class Event: + def __new__(cls) -> Event: + try: + return get_async_backend().create_event() + except AsyncLibraryNotFoundError: + return EventAdapter() + + def set(self) -> None: + """Set the flag, notifying all listeners.""" + raise NotImplementedError + + def is_set(self) -> bool: + """Return ``True`` if the flag is set, ``False`` if not.""" + raise NotImplementedError + + async def wait(self) -> None: + """ + Wait until the flag has been set. + + If the flag has already been set when this method is called, it returns + immediately. + + """ + raise NotImplementedError + + def statistics(self) -> EventStatistics: + """Return statistics about the current state of this event.""" + raise NotImplementedError + + +class EventAdapter(Event): + _internal_event: Event | None = None + _is_set: bool = False + + def __new__(cls) -> EventAdapter: + return object.__new__(cls) + + @property + def _event(self) -> Event: + if self._internal_event is None: + self._internal_event = get_async_backend().create_event() + if self._is_set: + self._internal_event.set() + + return self._internal_event + + def set(self) -> None: + if self._internal_event is None: + self._is_set = True + else: + self._event.set() + + def is_set(self) -> bool: + if self._internal_event is None: + return self._is_set + + return self._internal_event.is_set() + + async def wait(self) -> None: + await self._event.wait() + + def statistics(self) -> EventStatistics: + if self._internal_event is None: + return EventStatistics(tasks_waiting=0) + + return self._internal_event.statistics() + + +class Lock: + def __new__(cls, *, fast_acquire: bool = False) -> Lock: + try: + return get_async_backend().create_lock(fast_acquire=fast_acquire) + except AsyncLibraryNotFoundError: + return LockAdapter(fast_acquire=fast_acquire) + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + async def acquire(self) -> None: + """Acquire the lock.""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire the lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + raise NotImplementedError + + def release(self) -> None: + """Release the lock.""" + raise NotImplementedError + + def locked(self) -> bool: + """Return True if the lock is currently held.""" + raise NotImplementedError + + def statistics(self) -> LockStatistics: + """ + Return statistics about the current state of this lock. + + .. versionadded:: 3.0 + """ + raise NotImplementedError + + +class LockAdapter(Lock): + _internal_lock: Lock | None = None + + def __new__(cls, *, fast_acquire: bool = False) -> LockAdapter: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False): + self._fast_acquire = fast_acquire + + @property + def _lock(self) -> Lock: + if self._internal_lock is None: + self._internal_lock = get_async_backend().create_lock( + fast_acquire=self._fast_acquire + ) + + return self._internal_lock + + async def __aenter__(self) -> None: + await self._lock.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self._internal_lock is not None: + self._internal_lock.release() + + async def acquire(self) -> None: + """Acquire the lock.""" + await self._lock.acquire() + + def acquire_nowait(self) -> None: + """ + Acquire the lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + self._lock.acquire_nowait() + + def release(self) -> None: + """Release the lock.""" + self._lock.release() + + def locked(self) -> bool: + """Return True if the lock is currently held.""" + return self._lock.locked() + + def statistics(self) -> LockStatistics: + """ + Return statistics about the current state of this lock. + + .. versionadded:: 3.0 + + """ + if self._internal_lock is None: + return LockStatistics(False, None, 0) + + return self._internal_lock.statistics() + + +class Condition: + _owner_task: TaskInfo | None = None + + def __init__(self, lock: Lock | None = None): + self._lock = lock or Lock() + self._waiters: deque[Event] = deque() + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + def _check_acquired(self) -> None: + if self._owner_task != get_current_task(): + raise RuntimeError("The current task is not holding the underlying lock") + + async def acquire(self) -> None: + """Acquire the underlying lock.""" + await self._lock.acquire() + self._owner_task = get_current_task() + + def acquire_nowait(self) -> None: + """ + Acquire the underlying lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + self._lock.acquire_nowait() + self._owner_task = get_current_task() + + def release(self) -> None: + """Release the underlying lock.""" + self._lock.release() + + def locked(self) -> bool: + """Return True if the lock is set.""" + return self._lock.locked() + + def notify(self, n: int = 1) -> None: + """Notify exactly n listeners.""" + self._check_acquired() + for _ in range(n): + try: + event = self._waiters.popleft() + except IndexError: + break + + event.set() + + def notify_all(self) -> None: + """Notify all the listeners.""" + self._check_acquired() + for event in self._waiters: + event.set() + + self._waiters.clear() + + async def wait(self) -> None: + """Wait for a notification.""" + await checkpoint() + event = Event() + self._waiters.append(event) + self.release() + try: + await event.wait() + except BaseException: + if not event.is_set(): + self._waiters.remove(event) + + raise + finally: + with CancelScope(shield=True): + await self.acquire() + + def statistics(self) -> ConditionStatistics: + """ + Return statistics about the current state of this condition. + + .. versionadded:: 3.0 + """ + return ConditionStatistics(len(self._waiters), self._lock.statistics()) + + +class Semaphore: + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + try: + return get_async_backend().create_semaphore( + initial_value, max_value=max_value, fast_acquire=fast_acquire + ) + except AsyncLibraryNotFoundError: + return SemaphoreAdapter(initial_value, max_value=max_value) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ): + if not isinstance(initial_value, int): + raise TypeError("initial_value must be an integer") + if initial_value < 0: + raise ValueError("initial_value must be >= 0") + if max_value is not None: + if not isinstance(max_value, int): + raise TypeError("max_value must be an integer or None") + if max_value < initial_value: + raise ValueError( + "max_value must be equal to or higher than initial_value" + ) + + self._fast_acquire = fast_acquire + + async def __aenter__(self) -> Semaphore: + await self.acquire() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + async def acquire(self) -> None: + """Decrement the semaphore value, blocking if necessary.""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire the underlying lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + raise NotImplementedError + + def release(self) -> None: + """Increment the semaphore value.""" + raise NotImplementedError + + @property + def value(self) -> int: + """The current value of the semaphore.""" + raise NotImplementedError + + @property + def max_value(self) -> int | None: + """The maximum value of the semaphore.""" + raise NotImplementedError + + def statistics(self) -> SemaphoreStatistics: + """ + Return statistics about the current state of this semaphore. + + .. versionadded:: 3.0 + """ + raise NotImplementedError + + +class SemaphoreAdapter(Semaphore): + _internal_semaphore: Semaphore | None = None + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> SemaphoreAdapter: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> None: + super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) + self._initial_value = initial_value + self._max_value = max_value + + @property + def _semaphore(self) -> Semaphore: + if self._internal_semaphore is None: + self._internal_semaphore = get_async_backend().create_semaphore( + self._initial_value, max_value=self._max_value + ) + + return self._internal_semaphore + + async def acquire(self) -> None: + await self._semaphore.acquire() + + def acquire_nowait(self) -> None: + self._semaphore.acquire_nowait() + + def release(self) -> None: + self._semaphore.release() + + @property + def value(self) -> int: + if self._internal_semaphore is None: + return self._initial_value + + return self._semaphore.value + + @property + def max_value(self) -> int | None: + return self._max_value + + def statistics(self) -> SemaphoreStatistics: + if self._internal_semaphore is None: + return SemaphoreStatistics(tasks_waiting=0) + + return self._semaphore.statistics() + + +class CapacityLimiter: + def __new__(cls, total_tokens: float) -> CapacityLimiter: + try: + return get_async_backend().create_capacity_limiter(total_tokens) + except AsyncLibraryNotFoundError: + return CapacityLimiterAdapter(total_tokens) + + async def __aenter__(self) -> None: + raise NotImplementedError + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + raise NotImplementedError + + @property + def total_tokens(self) -> float: + """ + The total number of tokens available for borrowing. + + This is a read-write property. If the total number of tokens is increased, the + proportionate number of tasks waiting on this limiter will be granted their + tokens. + + .. versionchanged:: 3.0 + The property is now writable. + + """ + raise NotImplementedError + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + raise NotImplementedError + + @property + def borrowed_tokens(self) -> int: + """The number of tokens that have currently been borrowed.""" + raise NotImplementedError + + @property + def available_tokens(self) -> float: + """The number of tokens currently available to be borrowed""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire a token for the current task without waiting for one to become + available. + + :raises ~anyio.WouldBlock: if there are no tokens available for borrowing + + """ + raise NotImplementedError + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + """ + Acquire a token without waiting for one to become available. + + :param borrower: the entity borrowing a token + :raises ~anyio.WouldBlock: if there are no tokens available for borrowing + + """ + raise NotImplementedError + + async def acquire(self) -> None: + """ + Acquire a token for the current task, waiting if necessary for one to become + available. + + """ + raise NotImplementedError + + async def acquire_on_behalf_of(self, borrower: object) -> None: + """ + Acquire a token, waiting if necessary for one to become available. + + :param borrower: the entity borrowing a token + + """ + raise NotImplementedError + + def release(self) -> None: + """ + Release the token held by the current task. + + :raises RuntimeError: if the current task has not borrowed a token from this + limiter. + + """ + raise NotImplementedError + + def release_on_behalf_of(self, borrower: object) -> None: + """ + Release the token held by the given borrower. + + :raises RuntimeError: if the borrower has not borrowed a token from this + limiter. + + """ + raise NotImplementedError + + def statistics(self) -> CapacityLimiterStatistics: + """ + Return statistics about the current state of this limiter. + + .. versionadded:: 3.0 + + """ + raise NotImplementedError + + +class CapacityLimiterAdapter(CapacityLimiter): + _internal_limiter: CapacityLimiter | None = None + + def __new__(cls, total_tokens: float) -> CapacityLimiterAdapter: + return object.__new__(cls) + + def __init__(self, total_tokens: float) -> None: + self.total_tokens = total_tokens + + @property + def _limiter(self) -> CapacityLimiter: + if self._internal_limiter is None: + self._internal_limiter = get_async_backend().create_capacity_limiter( + self._total_tokens + ) + + return self._internal_limiter + + async def __aenter__(self) -> None: + await self._limiter.__aenter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + return await self._limiter.__aexit__(exc_type, exc_val, exc_tb) + + @property + def total_tokens(self) -> float: + if self._internal_limiter is None: + return self._total_tokens + + return self._internal_limiter.total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + if not isinstance(value, int) and value is not math.inf: + raise TypeError("total_tokens must be an int or math.inf") + elif value < 1: + raise ValueError("total_tokens must be >= 1") + + if self._internal_limiter is None: + self._total_tokens = value + return + + self._limiter.total_tokens = value + + @property + def borrowed_tokens(self) -> int: + if self._internal_limiter is None: + return 0 + + return self._internal_limiter.borrowed_tokens + + @property + def available_tokens(self) -> float: + if self._internal_limiter is None: + return self._total_tokens + + return self._internal_limiter.available_tokens + + def acquire_nowait(self) -> None: + self._limiter.acquire_nowait() + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + self._limiter.acquire_on_behalf_of_nowait(borrower) + + async def acquire(self) -> None: + await self._limiter.acquire() + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await self._limiter.acquire_on_behalf_of(borrower) + + def release(self) -> None: + self._limiter.release() + + def release_on_behalf_of(self, borrower: object) -> None: + self._limiter.release_on_behalf_of(borrower) + + def statistics(self) -> CapacityLimiterStatistics: + if self._internal_limiter is None: + return CapacityLimiterStatistics( + borrowed_tokens=0, + total_tokens=self.total_tokens, + borrowers=(), + tasks_waiting=0, + ) + + return self._internal_limiter.statistics() + + +class ResourceGuard: + """ + A context manager for ensuring that a resource is only used by a single task at a + time. + + Entering this context manager while the previous has not exited it yet will trigger + :exc:`BusyResourceError`. + + :param action: the action to guard against (visible in the :exc:`BusyResourceError` + when triggered, e.g. "Another task is already {action} this resource") + + .. versionadded:: 4.1 + """ + + __slots__ = "action", "_guarded" + + def __init__(self, action: str = "using"): + self.action: str = action + self._guarded = False + + def __enter__(self) -> None: + if self._guarded: + raise BusyResourceError(self.action) + + self._guarded = True + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._guarded = False diff --git a/.venv/Lib/site-packages/anyio/_core/_tasks.py b/.venv/Lib/site-packages/anyio/_core/_tasks.py new file mode 100644 index 0000000..fe49015 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_tasks.py @@ -0,0 +1,158 @@ +from __future__ import annotations + +import math +from collections.abc import Generator +from contextlib import contextmanager +from types import TracebackType + +from ..abc._tasks import TaskGroup, TaskStatus +from ._eventloop import get_async_backend + + +class _IgnoredTaskStatus(TaskStatus[object]): + def started(self, value: object = None) -> None: + pass + + +TASK_STATUS_IGNORED = _IgnoredTaskStatus() + + +class CancelScope: + """ + Wraps a unit of work that can be made separately cancellable. + + :param deadline: The time (clock value) when this scope is cancelled automatically + :param shield: ``True`` to shield the cancel scope from external cancellation + """ + + def __new__( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return get_async_backend().create_cancel_scope(shield=shield, deadline=deadline) + + def cancel(self) -> None: + """Cancel this scope immediately.""" + raise NotImplementedError + + @property + def deadline(self) -> float: + """ + The time (clock value) when this scope is cancelled automatically. + + Will be ``float('inf')`` if no timeout has been set. + + """ + raise NotImplementedError + + @deadline.setter + def deadline(self, value: float) -> None: + raise NotImplementedError + + @property + def cancel_called(self) -> bool: + """``True`` if :meth:`cancel` has been called.""" + raise NotImplementedError + + @property + def cancelled_caught(self) -> bool: + """ + ``True`` if this scope suppressed a cancellation exception it itself raised. + + This is typically used to check if any work was interrupted, or to see if the + scope was cancelled due to its deadline being reached. The value will, however, + only be ``True`` if the cancellation was triggered by the scope itself (and not + an outer scope). + + """ + raise NotImplementedError + + @property + def shield(self) -> bool: + """ + ``True`` if this scope is shielded from external cancellation. + + While a scope is shielded, it will not receive cancellations from outside. + + """ + raise NotImplementedError + + @shield.setter + def shield(self, value: bool) -> None: + raise NotImplementedError + + def __enter__(self) -> CancelScope: + raise NotImplementedError + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + raise NotImplementedError + + +@contextmanager +def fail_after( + delay: float | None, shield: bool = False +) -> Generator[CancelScope, None, None]: + """ + Create a context manager which raises a :class:`TimeoutError` if does not finish in + time. + + :param delay: maximum allowed time (in seconds) before raising the exception, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :return: a context manager that yields a cancel scope + :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\] + + """ + current_time = get_async_backend().current_time + deadline = (current_time() + delay) if delay is not None else math.inf + with get_async_backend().create_cancel_scope( + deadline=deadline, shield=shield + ) as cancel_scope: + yield cancel_scope + + if cancel_scope.cancelled_caught and current_time() >= cancel_scope.deadline: + raise TimeoutError + + +def move_on_after(delay: float | None, shield: bool = False) -> CancelScope: + """ + Create a cancel scope with a deadline that expires after the given delay. + + :param delay: maximum allowed time (in seconds) before exiting the context block, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :return: a cancel scope + + """ + deadline = ( + (get_async_backend().current_time() + delay) if delay is not None else math.inf + ) + return get_async_backend().create_cancel_scope(deadline=deadline, shield=shield) + + +def current_effective_deadline() -> float: + """ + Return the nearest deadline among all the cancel scopes effective for the current + task. + + :return: a clock value from the event loop's internal clock (or ``float('inf')`` if + there is no deadline in effect, or ``float('-inf')`` if the current scope has + been cancelled) + :rtype: float + + """ + return get_async_backend().current_effective_deadline() + + +def create_task_group() -> TaskGroup: + """ + Create a task group. + + :return: a task group + + """ + return get_async_backend().create_task_group() diff --git a/.venv/Lib/site-packages/anyio/_core/_tempfile.py b/.venv/Lib/site-packages/anyio/_core/_tempfile.py new file mode 100644 index 0000000..26d70ec --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_tempfile.py @@ -0,0 +1,616 @@ +from __future__ import annotations + +import os +import sys +import tempfile +from collections.abc import Iterable +from io import BytesIO, TextIOWrapper +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + AnyStr, + Generic, + overload, +) + +from .. import to_thread +from .._core._fileio import AsyncFile +from ..lowlevel import checkpoint_if_cancelled + +if TYPE_CHECKING: + from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer + + +class TemporaryFile(Generic[AnyStr]): + """ + An asynchronous temporary file that is automatically created and cleaned up. + + This class provides an asynchronous context manager interface to a temporary file. + The file is created using Python's standard `tempfile.TemporaryFile` function in a + background thread, and is wrapped as an asynchronous file using `AsyncFile`. + + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file. Only applicable in + text mode. + :param newline: Controls how universal newlines mode works (only applicable in text + mode). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param errors: The error handling scheme used for encoding/decoding errors. + """ + + _async_file: AsyncFile[AnyStr] + + @overload + def __init__( + self: TemporaryFile[bytes], + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + @overload + def __init__( + self: TemporaryFile[str], + mode: OpenTextMode, + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + + def __init__( + self, + mode: OpenTextMode | OpenBinaryMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: + self.mode = mode + self.buffering = buffering + self.encoding = encoding + self.newline = newline + self.suffix: str | None = suffix + self.prefix: str | None = prefix + self.dir: str | None = dir + self.errors = errors + + async def __aenter__(self) -> AsyncFile[AnyStr]: + fp = await to_thread.run_sync( + lambda: tempfile.TemporaryFile( + self.mode, + self.buffering, + self.encoding, + self.newline, + self.suffix, + self.prefix, + self.dir, + errors=self.errors, + ) + ) + self._async_file = AsyncFile(fp) + return self._async_file + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self._async_file.aclose() + + +class NamedTemporaryFile(Generic[AnyStr]): + """ + An asynchronous named temporary file that is automatically created and cleaned up. + + This class provides an asynchronous context manager for a temporary file with a + visible name in the file system. It uses Python's standard + :func:`~tempfile.NamedTemporaryFile` function and wraps the file object with + :class:`AsyncFile` for asynchronous operations. + + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file. Only applicable in + text mode. + :param newline: Controls how universal newlines mode works (only applicable in text + mode). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param delete: Whether to delete the file when it is closed. + :param errors: The error handling scheme used for encoding/decoding errors. + :param delete_on_close: (Python 3.12+) Whether to delete the file on close. + """ + + _async_file: AsyncFile[AnyStr] + + @overload + def __init__( + self: NamedTemporaryFile[bytes], + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + delete: bool = ..., + *, + errors: str | None = ..., + delete_on_close: bool = ..., + ): ... + @overload + def __init__( + self: NamedTemporaryFile[str], + mode: OpenTextMode, + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + delete: bool = ..., + *, + errors: str | None = ..., + delete_on_close: bool = ..., + ): ... + + def __init__( + self, + mode: OpenBinaryMode | OpenTextMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + delete: bool = True, + *, + errors: str | None = None, + delete_on_close: bool = True, + ) -> None: + self._params: dict[str, Any] = { + "mode": mode, + "buffering": buffering, + "encoding": encoding, + "newline": newline, + "suffix": suffix, + "prefix": prefix, + "dir": dir, + "delete": delete, + "errors": errors, + } + if sys.version_info >= (3, 12): + self._params["delete_on_close"] = delete_on_close + + async def __aenter__(self) -> AsyncFile[AnyStr]: + fp = await to_thread.run_sync( + lambda: tempfile.NamedTemporaryFile(**self._params) + ) + self._async_file = AsyncFile(fp) + return self._async_file + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self._async_file.aclose() + + +class SpooledTemporaryFile(AsyncFile[AnyStr]): + """ + An asynchronous spooled temporary file that starts in memory and is spooled to disk. + + This class provides an asynchronous interface to a spooled temporary file, much like + Python's standard :class:`~tempfile.SpooledTemporaryFile`. It supports asynchronous + write operations and provides a method to force a rollover to disk. + + :param max_size: Maximum size in bytes before the file is rolled over to disk. + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file (text mode only). + :param newline: Controls how universal newlines mode works (text mode only). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param errors: The error handling scheme used for encoding/decoding errors. + """ + + _rolled: bool = False + + @overload + def __init__( + self: SpooledTemporaryFile[bytes], + max_size: int = ..., + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + @overload + def __init__( + self: SpooledTemporaryFile[str], + max_size: int = ..., + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + + def __init__( + self, + max_size: int = 0, + mode: OpenBinaryMode | OpenTextMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: + self._tempfile_params: dict[str, Any] = { + "mode": mode, + "buffering": buffering, + "encoding": encoding, + "newline": newline, + "suffix": suffix, + "prefix": prefix, + "dir": dir, + "errors": errors, + } + self._max_size = max_size + if "b" in mode: + super().__init__(BytesIO()) # type: ignore[arg-type] + else: + super().__init__( + TextIOWrapper( # type: ignore[arg-type] + BytesIO(), + encoding=encoding, + errors=errors, + newline=newline, + write_through=True, + ) + ) + + async def aclose(self) -> None: + if not self._rolled: + self._fp.close() + return + + await super().aclose() + + async def _check(self) -> None: + if self._rolled or self._fp.tell() < self._max_size: + return + + await self.rollover() + + async def rollover(self) -> None: + if self._rolled: + return + + self._rolled = True + buffer = self._fp + buffer.seek(0) + self._fp = await to_thread.run_sync( + lambda: tempfile.TemporaryFile(**self._tempfile_params) + ) + await self.write(buffer.read()) + buffer.close() + + @property + def closed(self) -> bool: + return self._fp.closed + + async def read(self, size: int = -1) -> AnyStr: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.read(size) + + return await super().read(size) # type: ignore[return-value] + + async def read1(self: SpooledTemporaryFile[bytes], size: int = -1) -> bytes: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.read1(size) + + return await super().read1(size) + + async def readline(self) -> AnyStr: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readline() + + return await super().readline() # type: ignore[return-value] + + async def readlines(self) -> list[AnyStr]: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readlines() + + return await super().readlines() # type: ignore[return-value] + + async def readinto(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + self._fp.readinto(b) + + return await super().readinto(b) + + async def readinto1(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + self._fp.readinto(b) + + return await super().readinto1(b) + + async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.seek(offset, whence) + + return await super().seek(offset, whence) + + async def tell(self) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.tell() + + return await super().tell() + + async def truncate(self, size: int | None = None) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.truncate(size) + + return await super().truncate(size) + + @overload + async def write(self: SpooledTemporaryFile[bytes], b: ReadableBuffer) -> int: ... + @overload + async def write(self: SpooledTemporaryFile[str], b: str) -> int: ... + + async def write(self, b: ReadableBuffer | str) -> int: + """ + Asynchronously write data to the spooled temporary file. + + If the file has not yet been rolled over, the data is written synchronously, + and a rollover is triggered if the size exceeds the maximum size. + + :param s: The data to write. + :return: The number of bytes written. + :raises RuntimeError: If the underlying file is not initialized. + + """ + if not self._rolled: + await checkpoint_if_cancelled() + result = self._fp.write(b) + await self._check() + return result + + return await super().write(b) # type: ignore[misc] + + @overload + async def writelines( + self: SpooledTemporaryFile[bytes], lines: Iterable[ReadableBuffer] + ) -> None: ... + @overload + async def writelines( + self: SpooledTemporaryFile[str], lines: Iterable[str] + ) -> None: ... + + async def writelines(self, lines: Iterable[str] | Iterable[ReadableBuffer]) -> None: + """ + Asynchronously write a list of lines to the spooled temporary file. + + If the file has not yet been rolled over, the lines are written synchronously, + and a rollover is triggered if the size exceeds the maximum size. + + :param lines: An iterable of lines to write. + :raises RuntimeError: If the underlying file is not initialized. + + """ + if not self._rolled: + await checkpoint_if_cancelled() + result = self._fp.writelines(lines) + await self._check() + return result + + return await super().writelines(lines) # type: ignore[misc] + + +class TemporaryDirectory(Generic[AnyStr]): + """ + An asynchronous temporary directory that is created and cleaned up automatically. + + This class provides an asynchronous context manager for creating a temporary + directory. It wraps Python's standard :class:`~tempfile.TemporaryDirectory` to + perform directory creation and cleanup operations in a background thread. + + :param suffix: Suffix to be added to the temporary directory name. + :param prefix: Prefix to be added to the temporary directory name. + :param dir: The parent directory where the temporary directory is created. + :param ignore_cleanup_errors: Whether to ignore errors during cleanup + (Python 3.10+). + :param delete: Whether to delete the directory upon closing (Python 3.12+). + """ + + def __init__( + self, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, + *, + ignore_cleanup_errors: bool = False, + delete: bool = True, + ) -> None: + self.suffix: AnyStr | None = suffix + self.prefix: AnyStr | None = prefix + self.dir: AnyStr | None = dir + self.ignore_cleanup_errors = ignore_cleanup_errors + self.delete = delete + + self._tempdir: tempfile.TemporaryDirectory | None = None + + async def __aenter__(self) -> str: + params: dict[str, Any] = { + "suffix": self.suffix, + "prefix": self.prefix, + "dir": self.dir, + } + if sys.version_info >= (3, 10): + params["ignore_cleanup_errors"] = self.ignore_cleanup_errors + + if sys.version_info >= (3, 12): + params["delete"] = self.delete + + self._tempdir = await to_thread.run_sync( + lambda: tempfile.TemporaryDirectory(**params) + ) + return await to_thread.run_sync(self._tempdir.__enter__) + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + if self._tempdir is not None: + await to_thread.run_sync( + self._tempdir.__exit__, exc_type, exc_value, traceback + ) + + async def cleanup(self) -> None: + if self._tempdir is not None: + await to_thread.run_sync(self._tempdir.cleanup) + + +@overload +async def mkstemp( + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + text: bool = False, +) -> tuple[int, str]: ... + + +@overload +async def mkstemp( + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: bytes | None = None, + text: bool = False, +) -> tuple[int, bytes]: ... + + +async def mkstemp( + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, + text: bool = False, +) -> tuple[int, str | bytes]: + """ + Asynchronously create a temporary file and return an OS-level handle and the file + name. + + This function wraps `tempfile.mkstemp` and executes it in a background thread. + + :param suffix: Suffix to be added to the file name. + :param prefix: Prefix to be added to the file name. + :param dir: Directory in which the temporary file is created. + :param text: Whether the file is opened in text mode. + :return: A tuple containing the file descriptor and the file name. + + """ + return await to_thread.run_sync(tempfile.mkstemp, suffix, prefix, dir, text) + + +@overload +async def mkdtemp( + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, +) -> str: ... + + +@overload +async def mkdtemp( + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: bytes | None = None, +) -> bytes: ... + + +async def mkdtemp( + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, +) -> str | bytes: + """ + Asynchronously create a temporary directory and return its path. + + This function wraps `tempfile.mkdtemp` and executes it in a background thread. + + :param suffix: Suffix to be added to the directory name. + :param prefix: Prefix to be added to the directory name. + :param dir: Parent directory where the temporary directory is created. + :return: The path of the created temporary directory. + + """ + return await to_thread.run_sync(tempfile.mkdtemp, suffix, prefix, dir) + + +async def gettempdir() -> str: + """ + Asynchronously return the name of the directory used for temporary files. + + This function wraps `tempfile.gettempdir` and executes it in a background thread. + + :return: The path of the temporary directory as a string. + + """ + return await to_thread.run_sync(tempfile.gettempdir) + + +async def gettempdirb() -> bytes: + """ + Asynchronously return the name of the directory used for temporary files in bytes. + + This function wraps `tempfile.gettempdirb` and executes it in a background thread. + + :return: The path of the temporary directory as bytes. + + """ + return await to_thread.run_sync(tempfile.gettempdirb) diff --git a/.venv/Lib/site-packages/anyio/_core/_testing.py b/.venv/Lib/site-packages/anyio/_core/_testing.py new file mode 100644 index 0000000..9e28b22 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_testing.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +from collections.abc import Awaitable, Generator +from typing import Any, cast + +from ._eventloop import get_async_backend + + +class TaskInfo: + """ + Represents an asynchronous task. + + :ivar int id: the unique identifier of the task + :ivar parent_id: the identifier of the parent task, if any + :vartype parent_id: Optional[int] + :ivar str name: the description of the task (if any) + :ivar ~collections.abc.Coroutine coro: the coroutine object of the task + """ + + __slots__ = "_name", "id", "parent_id", "name", "coro" + + def __init__( + self, + id: int, + parent_id: int | None, + name: str | None, + coro: Generator[Any, Any, Any] | Awaitable[Any], + ): + func = get_current_task + self._name = f"{func.__module__}.{func.__qualname__}" + self.id: int = id + self.parent_id: int | None = parent_id + self.name: str | None = name + self.coro: Generator[Any, Any, Any] | Awaitable[Any] = coro + + def __eq__(self, other: object) -> bool: + if isinstance(other, TaskInfo): + return self.id == other.id + + return NotImplemented + + def __hash__(self) -> int: + return hash(self.id) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(id={self.id!r}, name={self.name!r})" + + def has_pending_cancellation(self) -> bool: + """ + Return ``True`` if the task has a cancellation pending, ``False`` otherwise. + + """ + return False + + +def get_current_task() -> TaskInfo: + """ + Return the current task. + + :return: a representation of the current task + + """ + return get_async_backend().get_current_task() + + +def get_running_tasks() -> list[TaskInfo]: + """ + Return a list of running tasks in the current event loop. + + :return: a list of task info objects + + """ + return cast("list[TaskInfo]", get_async_backend().get_running_tasks()) + + +async def wait_all_tasks_blocked() -> None: + """Wait until all other tasks are waiting for something.""" + await get_async_backend().wait_all_tasks_blocked() diff --git a/.venv/Lib/site-packages/anyio/_core/_typedattr.py b/.venv/Lib/site-packages/anyio/_core/_typedattr.py new file mode 100644 index 0000000..f358a44 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/_core/_typedattr.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from collections.abc import Callable, Mapping +from typing import Any, TypeVar, final, overload + +from ._exceptions import TypedAttributeLookupError + +T_Attr = TypeVar("T_Attr") +T_Default = TypeVar("T_Default") +undefined = object() + + +def typed_attribute() -> Any: + """Return a unique object, used to mark typed attributes.""" + return object() + + +class TypedAttributeSet: + """ + Superclass for typed attribute collections. + + Checks that every public attribute of every subclass has a type annotation. + """ + + def __init_subclass__(cls) -> None: + annotations: dict[str, Any] = getattr(cls, "__annotations__", {}) + for attrname in dir(cls): + if not attrname.startswith("_") and attrname not in annotations: + raise TypeError( + f"Attribute {attrname!r} is missing its type annotation" + ) + + super().__init_subclass__() + + +class TypedAttributeProvider: + """Base class for classes that wish to provide typed extra attributes.""" + + @property + def extra_attributes(self) -> Mapping[T_Attr, Callable[[], T_Attr]]: + """ + A mapping of the extra attributes to callables that return the corresponding + values. + + If the provider wraps another provider, the attributes from that wrapper should + also be included in the returned mapping (but the wrapper may override the + callables from the wrapped instance). + + """ + return {} + + @overload + def extra(self, attribute: T_Attr) -> T_Attr: ... + + @overload + def extra(self, attribute: T_Attr, default: T_Default) -> T_Attr | T_Default: ... + + @final + def extra(self, attribute: Any, default: object = undefined) -> object: + """ + extra(attribute, default=undefined) + + Return the value of the given typed extra attribute. + + :param attribute: the attribute (member of a :class:`~TypedAttributeSet`) to + look for + :param default: the value that should be returned if no value is found for the + attribute + :raises ~anyio.TypedAttributeLookupError: if the search failed and no default + value was given + + """ + try: + getter = self.extra_attributes[attribute] + except KeyError: + if default is undefined: + raise TypedAttributeLookupError("Attribute not found") from None + else: + return default + + return getter() diff --git a/.venv/Lib/site-packages/anyio/abc/__init__.py b/.venv/Lib/site-packages/anyio/abc/__init__.py new file mode 100644 index 0000000..3d3b61c --- /dev/null +++ b/.venv/Lib/site-packages/anyio/abc/__init__.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from ._eventloop import AsyncBackend as AsyncBackend +from ._resources import AsyncResource as AsyncResource +from ._sockets import ConnectedUDPSocket as ConnectedUDPSocket +from ._sockets import ConnectedUNIXDatagramSocket as ConnectedUNIXDatagramSocket +from ._sockets import IPAddressType as IPAddressType +from ._sockets import IPSockAddrType as IPSockAddrType +from ._sockets import SocketAttribute as SocketAttribute +from ._sockets import SocketListener as SocketListener +from ._sockets import SocketStream as SocketStream +from ._sockets import UDPPacketType as UDPPacketType +from ._sockets import UDPSocket as UDPSocket +from ._sockets import UNIXDatagramPacketType as UNIXDatagramPacketType +from ._sockets import UNIXDatagramSocket as UNIXDatagramSocket +from ._sockets import UNIXSocketStream as UNIXSocketStream +from ._streams import AnyByteReceiveStream as AnyByteReceiveStream +from ._streams import AnyByteSendStream as AnyByteSendStream +from ._streams import AnyByteStream as AnyByteStream +from ._streams import AnyUnreliableByteReceiveStream as AnyUnreliableByteReceiveStream +from ._streams import AnyUnreliableByteSendStream as AnyUnreliableByteSendStream +from ._streams import AnyUnreliableByteStream as AnyUnreliableByteStream +from ._streams import ByteReceiveStream as ByteReceiveStream +from ._streams import ByteSendStream as ByteSendStream +from ._streams import ByteStream as ByteStream +from ._streams import Listener as Listener +from ._streams import ObjectReceiveStream as ObjectReceiveStream +from ._streams import ObjectSendStream as ObjectSendStream +from ._streams import ObjectStream as ObjectStream +from ._streams import UnreliableObjectReceiveStream as UnreliableObjectReceiveStream +from ._streams import UnreliableObjectSendStream as UnreliableObjectSendStream +from ._streams import UnreliableObjectStream as UnreliableObjectStream +from ._subprocesses import Process as Process +from ._tasks import TaskGroup as TaskGroup +from ._tasks import TaskStatus as TaskStatus +from ._testing import TestRunner as TestRunner + +# Re-exported here, for backwards compatibility +# isort: off +from .._core._synchronization import ( + CapacityLimiter as CapacityLimiter, + Condition as Condition, + Event as Event, + Lock as Lock, + Semaphore as Semaphore, +) +from .._core._tasks import CancelScope as CancelScope +from ..from_thread import BlockingPortal as BlockingPortal + +# Re-export imports so they look like they live directly in this package +for __value in list(locals().values()): + if getattr(__value, "__module__", "").startswith("anyio.abc."): + __value.__module__ = __name__ + +del __value diff --git a/.venv/Lib/site-packages/anyio/abc/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/abc/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69861ea182a5a289318ca81b0758a81ca5693d24 GIT binary patch literal 2357 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/abc/__pycache__/_resources.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/abc/__pycache__/_resources.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a8745fe4415aef0d6784339a5cac74e8e972fdb GIT binary patch literal 1769 zcmZux&2Jk;6rWk|uGgPQ8bfGWwW~;gEP}5{5kgH<0u?2yngY^dv>oqGV#oVoX4Xk; zdI(K~ia_Gl@GnGxzk>reE=4Ov0`&mgT#OL(#Cv1A#e%Us`@ZJQd%ySId|xW%39J)u zr8$!!;rL3eB#r9gw&xFX)Vy4jQZ-K;c1kFrju2gOkjmMCl}_OyyEqs z5Eh-HqK%*wmYp&s8sTN%Jf)*MoEeS0Mf~g$@hzVFCU*+4;BsbrS~dS0l2w`(SQNzx zOImRxH>z5iUA?;YE>BomW(}DL<|QFdnz0Y2rQ5*r2J_mtdmRpfEPmf-BBi%IbT?V! zi|BA2B*%mkhx)|Pd%Ibu8-Mg+*8!t; z{cWfsq;xIbiR-mp9_`j|v>J8UO8BcCP(i-3&Z1r`uCs<$cb{R_>7{wsWhzbA?Y}Tp zl-e^*1!9vJ?XTpo1M=g{{Qbh;QV0HC#-JV&lZD-)GQI-DB;%cbV=}g!^ur;H$|rE@ zfp(nv%rF3>9(j#?X&^?T1jf*s<_PPmE@nWGT72Jg6X@17i_|U)Qd5ZtCPGFqCjL3p2%G3J_1H^x{9 z3v{VDYGcJzErHeQFCsu6UWGw=bKm0~d=fsg@7f!2#Km)HP?^$ARL?8b)Y~vooyV(m zbWqg_PYVnMc4hf%Yv~c>$-mIff#BcgE*0{E9TG#VlVI^ zRVb*D7y?H|$l%*6aKpd61=|sMtQm$iD3iJQgX=?`8kK=*8W(>$w>&W5J21()mj+p+ zEHd}vAcs_*?n>cRs7=?HA> z)M|8FK>`m&TD;Qfxlg(CZl{-UnbhDM7l?ID@I3(onC6~}((FW*hr?*@!fTs>krX&V z3WU_avPpp@Pfeg((YDB<77ML{R1l3~R!wfbK7#xWu?!|SY4R1=1{q4}8Cm)TA$v$( fJ|maU$l}j>;r`m;jRX3Jxj>h`-u{!oM>+ckJc-cx literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc b/.venv/Lib/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8a11b9c41688ca5daf191c4a7319e29098fba3a GIT binary patch literal 1655 zcmZux-ESL35Z}EopY41!jUkX$?J5#QmP-1F6d^>K5~wIq)fA8pqs#hk6Fa_-uzOD9 z(1*}Os0bw98vcbS@OSXQ8!w=fB7t}S-dv0j^of~0Tg8I4y!V@(+1Z)j%r6r46nl+R$cu z?QBt7oseKyFP*Ld^C2O0Fa_W(LV>^gWSx9%-lHdUO;%y`gyt3=r*I#8lCCT@-cdrT z#yXE1H-zeB=}u$49SU|^rrRRORAZH^Z29(0wyW4GaMWqM2~~uYZlv33V{um`yNz4z zW<#|z@zM^cAYawsNw1w=C`z!hDfc!8$ z`>^`AvOs=d+B85g8rUs5rasB*)KYuc0P;0cQ1VBUU;G=K=+__i9Vtitz+jB{r~!+RgrCvV&rvWe|7~sR(IBj z@WrC!61$YRL^SF*xN8tJ O=%8*$6y!Wi^>Xg|e!^qn z``Aoz8g?VZ6Tbgpmq$ZQ$@jxFfMuwOiqcH^zMMfH$i2kZQnR41!~i5xFoO@Q#0mfE z4opYnv0>Y8e~Qe^9^4#Q)UNd%$G-UE`Kx^!zI}(BztS%ua>>jK{W78osZ0&3h|UqG z_Pastg@Fm^F(S#ptv&==tqhkNsE3zltzN}^HoNdbKt2*XJ2&A1N$htWa&9BZN@9U> z2XH&tk|oq)S+q-?P;r88N%xh4pjYJ;Fu{AF5cEw-=^0u0g}iu1E}xP4pRDS`mBU*H R^bco_E_}K5CxMUl^A9|rr!fEk literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/abc/__pycache__/_sockets.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/abc/__pycache__/_sockets.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54e50f94039a3d54c9988df9632fb86608e503d6 GIT binary patch literal 9993 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 G!~Ykwn`SEj literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/abc/__pycache__/_streams.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/abc/__pycache__/_streams.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10557f1b9cc30806b0b46ea5b22333ede4abbb3c GIT binary patch literal 8573 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 O?da+MR`4$T^Zx?M8=I&A literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0118434e398b74492ca682e5e06a669987578e3d GIT binary patch literal 3377 zcmcInOK%)S5bmCxeLrl+i69Y*S`iUhk&FaV6tqGRM@bwj#x?reJ&BLyuYt8^DxWm>lh^o;Kp8N{TJw#C(Td1vAxSgY_RkM`4>Vw$6od-$X z%4H;lP@-leMUXVCd`42D<#46Kt}I=#66$8G;y$`+sG9}A(g@OdwNN)_mG{xrV_jt* zU1L~n9tw)9%r`0Lqq7ShF(iJQ%=i2OC)<>9z@GT)q-8n==dCUu^!kyxw(9q(*VRl3 zeex;5e2EaA)PT8)Fkm>uU*~pkQyG0yA@O(1umu@-!gh$*q$FOD(9dkawMuH%VpI&6 z*9wiKl9;|lWi**tsrAZzbRP#Qr_MT;A(4NW+3MWnkV9w9a61<%-x7Ymvu48@kn(M6 z3f{SFaIvtuM7BA(3>j{9t{CPPW#poN+wW-G)Z6YX+r19A1wGRTm9as2$MA->-!Xb- zN8iPRa;-lM)E&reREr_iG+LIA6vS(r$=eesp?$c zwno*&I}FYq%u8167hP+@o;bSA36V z+rx9xsYDUTCOtZoWVI`69W1$bWKMA1!<9|eeHI>4E)35ym_=-_KM=2oa-Ivz_D1z2 zqHuWOV8OimqG0@ic=c!s;2tK78kullym+v9-g{9zlrg~Tz)``psi@`sI->P$571P0xmWgRluBlPC~psjdeVU3YzJ;7Gov>z@t`C)N~o-SSOcXYxK}bCS$U@|q;afCQyJg9i)~L-vN0 z9hZbiazc_fB?+%0*(SrL!K#vzL_VJRBOrItkLrg%=jWd)`Fw4XLxsa*$&DMpJylAn z47l;(=gNwy9dP4AsL=R9E{XGJ!X+hXj8zzp3yDsRr^GrKj%$ey=g;HmiRba|Oq^fX zqg|NNE@re(!093mFN!d4cq{_q>jQX|0pbjrAi&rg*p9G09@NhFhk80Xs`ufU&4mWf zMnD;!ks6~r4Bk;eITZ!PL{@>x(p)!Kkb{?1v~OuefnOFs>NzWhe)Xs~a0Y*15GzX( zZqW3m1*P|+3i*JY0~6WDc@xMuhcSMLPW&b}@q2XqAv*R5o%{)%`=f9P<3;?H`zH#S HF#CT2ssCB4 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc b/.venv/Lib/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d0efde6b0e0f1c48b204b2dcfe13678a8545d19 GIT binary patch literal 3263 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc b/.venv/Lib/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75732103f6522a65d2adb4e1fb07a042b52ec27b GIT binary patch literal 4539 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 idt~8vvam;{|1_!(mVQ{dum4^8P^= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + +if TYPE_CHECKING: + from _typeshed import HasFileno + + from .._core._synchronization import CapacityLimiter, Event, Lock, Semaphore + from .._core._tasks import CancelScope + from .._core._testing import TaskInfo + from ..from_thread import BlockingPortal + from ._sockets import ( + ConnectedUDPSocket, + ConnectedUNIXDatagramSocket, + IPSockAddrType, + SocketListener, + SocketStream, + UDPSocket, + UNIXDatagramSocket, + UNIXSocketStream, + ) + from ._subprocesses import Process + from ._tasks import TaskGroup + from ._testing import TestRunner + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") +StrOrBytesPath: TypeAlias = Union[str, bytes, "PathLike[str]", "PathLike[bytes]"] + + +class AsyncBackend(metaclass=ABCMeta): + @classmethod + @abstractmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + """ + Run the given coroutine function in an asynchronous event loop. + + The current thread must not be already running an event loop. + + :param func: a coroutine function + :param args: positional arguments to ``func`` + :param kwargs: positional arguments to ``func`` + :param options: keyword arguments to call the backend ``run()`` implementation + with + :return: the return value of the coroutine function + """ + + @classmethod + @abstractmethod + def current_token(cls) -> object: + """ + + :return: + """ + + @classmethod + @abstractmethod + def current_time(cls) -> float: + """ + Return the current value of the event loop's internal clock. + + :return: the clock value (seconds) + """ + + @classmethod + @abstractmethod + def cancelled_exception_class(cls) -> type[BaseException]: + """Return the exception class that is raised in a task if it's cancelled.""" + + @classmethod + @abstractmethod + async def checkpoint(cls) -> None: + """ + Check if the task has been cancelled, and allow rescheduling of other tasks. + + This is effectively the same as running :meth:`checkpoint_if_cancelled` and then + :meth:`cancel_shielded_checkpoint`. + """ + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + """ + Check if the current task group has been cancelled. + + This will check if the task has been cancelled, but will not allow other tasks + to be scheduled if not. + + """ + if cls.current_effective_deadline() == -math.inf: + await cls.checkpoint() + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + """ + Allow the rescheduling of other tasks. + + This will give other tasks the opportunity to run, but without checking if the + current task group has been cancelled, unlike with :meth:`checkpoint`. + + """ + with cls.create_cancel_scope(shield=True): + await cls.sleep(0) + + @classmethod + @abstractmethod + async def sleep(cls, delay: float) -> None: + """ + Pause the current task for the specified duration. + + :param delay: the duration, in seconds + """ + + @classmethod + @abstractmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + pass + + @classmethod + @abstractmethod + def current_effective_deadline(cls) -> float: + """ + Return the nearest deadline among all the cancel scopes effective for the + current task. + + :return: + - a clock value from the event loop's internal clock + - ``inf`` if there is no deadline in effect + - ``-inf`` if the current scope has been cancelled + :rtype: float + """ + + @classmethod + @abstractmethod + def create_task_group(cls) -> TaskGroup: + pass + + @classmethod + @abstractmethod + def create_event(cls) -> Event: + pass + + @classmethod + @abstractmethod + def create_lock(cls, *, fast_acquire: bool) -> Lock: + pass + + @classmethod + @abstractmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + pass + + @classmethod + @abstractmethod + def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: + pass + + @classmethod + @abstractmethod + async def run_sync_in_worker_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: CapacityLimiter | None = None, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + def check_cancelled(cls) -> None: + pass + + @classmethod + @abstractmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + def create_blocking_portal(cls) -> BlockingPortal: + pass + + @classmethod + @abstractmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + pass + + @classmethod + @abstractmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[Process]) -> None: + pass + + @classmethod + @abstractmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> SocketStream: + pass + + @classmethod + @abstractmethod + async def connect_unix(cls, path: str | bytes) -> UNIXSocketStream: + pass + + @classmethod + @abstractmethod + def create_tcp_listener(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + def create_unix_listener(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + async def create_udp_socket( + cls, + family: AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + pass + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: None + ) -> UNIXDatagramSocket: ... + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: str | bytes + ) -> ConnectedUNIXDatagramSocket: ... + + @classmethod + @abstractmethod + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: str | bytes | None + ) -> UNIXDatagramSocket | ConnectedUNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + pass + + @classmethod + @abstractmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + pass + + @classmethod + @abstractmethod + async def wait_readable(cls, obj: HasFileno | int) -> None: + pass + + @classmethod + @abstractmethod + async def wait_writable(cls, obj: HasFileno | int) -> None: + pass + + @classmethod + @abstractmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + pass + + @classmethod + @abstractmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + pass + + @classmethod + @abstractmethod + def get_current_task(cls) -> TaskInfo: + pass + + @classmethod + @abstractmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + pass + + @classmethod + @abstractmethod + async def wait_all_tasks_blocked(cls) -> None: + pass + + @classmethod + @abstractmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + pass diff --git a/.venv/Lib/site-packages/anyio/abc/_resources.py b/.venv/Lib/site-packages/anyio/abc/_resources.py new file mode 100644 index 0000000..10df115 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/abc/_resources.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from abc import ABCMeta, abstractmethod +from types import TracebackType +from typing import TypeVar + +T = TypeVar("T") + + +class AsyncResource(metaclass=ABCMeta): + """ + Abstract base class for all closeable asynchronous resources. + + Works as an asynchronous context manager which returns the instance itself on enter, + and calls :meth:`aclose` on exit. + """ + + __slots__ = () + + async def __aenter__(self: T) -> T: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.aclose() + + @abstractmethod + async def aclose(self) -> None: + """Close the resource.""" diff --git a/.venv/Lib/site-packages/anyio/abc/_sockets.py b/.venv/Lib/site-packages/anyio/abc/_sockets.py new file mode 100644 index 0000000..1c6a450 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/abc/_sockets.py @@ -0,0 +1,194 @@ +from __future__ import annotations + +import socket +from abc import abstractmethod +from collections.abc import Callable, Collection, Mapping +from contextlib import AsyncExitStack +from io import IOBase +from ipaddress import IPv4Address, IPv6Address +from socket import AddressFamily +from types import TracebackType +from typing import Any, TypeVar, Union + +from .._core._typedattr import ( + TypedAttributeProvider, + TypedAttributeSet, + typed_attribute, +) +from ._streams import ByteStream, Listener, UnreliableObjectStream +from ._tasks import TaskGroup + +IPAddressType = Union[str, IPv4Address, IPv6Address] +IPSockAddrType = tuple[str, int] +SockAddrType = Union[IPSockAddrType, str] +UDPPacketType = tuple[bytes, IPSockAddrType] +UNIXDatagramPacketType = tuple[bytes, str] +T_Retval = TypeVar("T_Retval") + + +class _NullAsyncContextManager: + async def __aenter__(self) -> None: + pass + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + return None + + +class SocketAttribute(TypedAttributeSet): + #: the address family of the underlying socket + family: AddressFamily = typed_attribute() + #: the local socket address of the underlying socket + local_address: SockAddrType = typed_attribute() + #: for IP addresses, the local port the underlying socket is bound to + local_port: int = typed_attribute() + #: the underlying stdlib socket object + raw_socket: socket.socket = typed_attribute() + #: the remote address the underlying socket is connected to + remote_address: SockAddrType = typed_attribute() + #: for IP addresses, the remote port the underlying socket is connected to + remote_port: int = typed_attribute() + + +class _SocketProvider(TypedAttributeProvider): + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + from .._core._sockets import convert_ipv6_sockaddr as convert + + attributes: dict[Any, Callable[[], Any]] = { + SocketAttribute.family: lambda: self._raw_socket.family, + SocketAttribute.local_address: lambda: convert( + self._raw_socket.getsockname() + ), + SocketAttribute.raw_socket: lambda: self._raw_socket, + } + try: + peername: tuple[str, int] | None = convert(self._raw_socket.getpeername()) + except OSError: + peername = None + + # Provide the remote address for connected sockets + if peername is not None: + attributes[SocketAttribute.remote_address] = lambda: peername + + # Provide local and remote ports for IP based sockets + if self._raw_socket.family in (AddressFamily.AF_INET, AddressFamily.AF_INET6): + attributes[SocketAttribute.local_port] = ( + lambda: self._raw_socket.getsockname()[1] + ) + if peername is not None: + remote_port = peername[1] + attributes[SocketAttribute.remote_port] = lambda: remote_port + + return attributes + + @property + @abstractmethod + def _raw_socket(self) -> socket.socket: + pass + + +class SocketStream(ByteStream, _SocketProvider): + """ + Transports bytes over a socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + +class UNIXSocketStream(SocketStream): + @abstractmethod + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + """ + Send file descriptors along with a message to the peer. + + :param message: a non-empty bytestring + :param fds: a collection of files (either numeric file descriptors or open file + or socket objects) + """ + + @abstractmethod + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + """ + Receive file descriptors along with a message from the peer. + + :param msglen: length of the message to expect from the peer + :param maxfds: maximum number of file descriptors to expect from the peer + :return: a tuple of (message, file descriptors) + """ + + +class SocketListener(Listener[SocketStream], _SocketProvider): + """ + Listens to incoming socket connections. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @abstractmethod + async def accept(self) -> SocketStream: + """Accept an incoming connection.""" + + async def serve( + self, + handler: Callable[[SocketStream], Any], + task_group: TaskGroup | None = None, + ) -> None: + from .. import create_task_group + + async with AsyncExitStack() as stack: + if task_group is None: + task_group = await stack.enter_async_context(create_task_group()) + + while True: + stream = await self.accept() + task_group.start_soon(handler, stream) + + +class UDPSocket(UnreliableObjectStream[UDPPacketType], _SocketProvider): + """ + Represents an unconnected UDP socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + async def sendto(self, data: bytes, host: str, port: int) -> None: + """ + Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, (host, port))). + + """ + return await self.send((data, (host, port))) + + +class ConnectedUDPSocket(UnreliableObjectStream[bytes], _SocketProvider): + """ + Represents an connected UDP socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + +class UNIXDatagramSocket( + UnreliableObjectStream[UNIXDatagramPacketType], _SocketProvider +): + """ + Represents an unconnected Unix datagram socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + async def sendto(self, data: bytes, path: str) -> None: + """Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, path)).""" + return await self.send((data, path)) + + +class ConnectedUNIXDatagramSocket(UnreliableObjectStream[bytes], _SocketProvider): + """ + Represents a connected Unix datagram socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ diff --git a/.venv/Lib/site-packages/anyio/abc/_streams.py b/.venv/Lib/site-packages/anyio/abc/_streams.py new file mode 100644 index 0000000..f11d97b --- /dev/null +++ b/.venv/Lib/site-packages/anyio/abc/_streams.py @@ -0,0 +1,203 @@ +from __future__ import annotations + +from abc import abstractmethod +from collections.abc import Callable +from typing import Any, Generic, TypeVar, Union + +from .._core._exceptions import EndOfStream +from .._core._typedattr import TypedAttributeProvider +from ._resources import AsyncResource +from ._tasks import TaskGroup + +T_Item = TypeVar("T_Item") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class UnreliableObjectReceiveStream( + Generic[T_co], AsyncResource, TypedAttributeProvider +): + """ + An interface for receiving objects. + + This interface makes no guarantees that the received messages arrive in the order in + which they were sent, or that no messages are missed. + + Asynchronously iterating over objects of this type will yield objects matching the + given type parameter. + """ + + def __aiter__(self) -> UnreliableObjectReceiveStream[T_co]: + return self + + async def __anext__(self) -> T_co: + try: + return await self.receive() + except EndOfStream: + raise StopAsyncIteration + + @abstractmethod + async def receive(self) -> T_co: + """ + Receive the next item. + + :raises ~anyio.ClosedResourceError: if the receive stream has been explicitly + closed + :raises ~anyio.EndOfStream: if this stream has been closed from the other end + :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable + due to external causes + """ + + +class UnreliableObjectSendStream( + Generic[T_contra], AsyncResource, TypedAttributeProvider +): + """ + An interface for sending objects. + + This interface makes no guarantees that the messages sent will reach the + recipient(s) in the same order in which they were sent, or at all. + """ + + @abstractmethod + async def send(self, item: T_contra) -> None: + """ + Send an item to the peer(s). + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if the send stream has been explicitly + closed + :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable + due to external causes + """ + + +class UnreliableObjectStream( + UnreliableObjectReceiveStream[T_Item], UnreliableObjectSendStream[T_Item] +): + """ + A bidirectional message stream which does not guarantee the order or reliability of + message delivery. + """ + + +class ObjectReceiveStream(UnreliableObjectReceiveStream[T_co]): + """ + A receive message stream which guarantees that messages are received in the same + order in which they were sent, and that no messages are missed. + """ + + +class ObjectSendStream(UnreliableObjectSendStream[T_contra]): + """ + A send message stream which guarantees that messages are delivered in the same order + in which they were sent, without missing any messages in the middle. + """ + + +class ObjectStream( + ObjectReceiveStream[T_Item], + ObjectSendStream[T_Item], + UnreliableObjectStream[T_Item], +): + """ + A bidirectional message stream which guarantees the order and reliability of message + delivery. + """ + + @abstractmethod + async def send_eof(self) -> None: + """ + Send an end-of-file indication to the peer. + + You should not try to send any further data to this stream after calling this + method. This method is idempotent (does nothing on successive calls). + """ + + +class ByteReceiveStream(AsyncResource, TypedAttributeProvider): + """ + An interface for receiving bytes from a single peer. + + Iterating this byte stream will yield a byte string of arbitrary length, but no more + than 65536 bytes. + """ + + def __aiter__(self) -> ByteReceiveStream: + return self + + async def __anext__(self) -> bytes: + try: + return await self.receive() + except EndOfStream: + raise StopAsyncIteration + + @abstractmethod + async def receive(self, max_bytes: int = 65536) -> bytes: + """ + Receive at most ``max_bytes`` bytes from the peer. + + .. note:: Implementers of this interface should not return an empty + :class:`bytes` object, and users should ignore them. + + :param max_bytes: maximum number of bytes to receive + :return: the received bytes + :raises ~anyio.EndOfStream: if this stream has been closed from the other end + """ + + +class ByteSendStream(AsyncResource, TypedAttributeProvider): + """An interface for sending bytes to a single peer.""" + + @abstractmethod + async def send(self, item: bytes) -> None: + """ + Send the given bytes to the peer. + + :param item: the bytes to send + """ + + +class ByteStream(ByteReceiveStream, ByteSendStream): + """A bidirectional byte stream.""" + + @abstractmethod + async def send_eof(self) -> None: + """ + Send an end-of-file indication to the peer. + + You should not try to send any further data to this stream after calling this + method. This method is idempotent (does nothing on successive calls). + """ + + +#: Type alias for all unreliable bytes-oriented receive streams. +AnyUnreliableByteReceiveStream = Union[ + UnreliableObjectReceiveStream[bytes], ByteReceiveStream +] +#: Type alias for all unreliable bytes-oriented send streams. +AnyUnreliableByteSendStream = Union[UnreliableObjectSendStream[bytes], ByteSendStream] +#: Type alias for all unreliable bytes-oriented streams. +AnyUnreliableByteStream = Union[UnreliableObjectStream[bytes], ByteStream] +#: Type alias for all bytes-oriented receive streams. +AnyByteReceiveStream = Union[ObjectReceiveStream[bytes], ByteReceiveStream] +#: Type alias for all bytes-oriented send streams. +AnyByteSendStream = Union[ObjectSendStream[bytes], ByteSendStream] +#: Type alias for all bytes-oriented streams. +AnyByteStream = Union[ObjectStream[bytes], ByteStream] + + +class Listener(Generic[T_co], AsyncResource, TypedAttributeProvider): + """An interface for objects that let you accept incoming connections.""" + + @abstractmethod + async def serve( + self, handler: Callable[[T_co], Any], task_group: TaskGroup | None = None + ) -> None: + """ + Accept incoming connections as they come in and start tasks to handle them. + + :param handler: a callable that will be used to handle each accepted connection + :param task_group: the task group that will be used to start tasks for handling + each accepted connection (if omitted, an ad-hoc task group will be created) + """ diff --git a/.venv/Lib/site-packages/anyio/abc/_subprocesses.py b/.venv/Lib/site-packages/anyio/abc/_subprocesses.py new file mode 100644 index 0000000..ce0564c --- /dev/null +++ b/.venv/Lib/site-packages/anyio/abc/_subprocesses.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from abc import abstractmethod +from signal import Signals + +from ._resources import AsyncResource +from ._streams import ByteReceiveStream, ByteSendStream + + +class Process(AsyncResource): + """An asynchronous version of :class:`subprocess.Popen`.""" + + @abstractmethod + async def wait(self) -> int: + """ + Wait until the process exits. + + :return: the exit code of the process + """ + + @abstractmethod + def terminate(self) -> None: + """ + Terminates the process, gracefully if possible. + + On Windows, this calls ``TerminateProcess()``. + On POSIX systems, this sends ``SIGTERM`` to the process. + + .. seealso:: :meth:`subprocess.Popen.terminate` + """ + + @abstractmethod + def kill(self) -> None: + """ + Kills the process. + + On Windows, this calls ``TerminateProcess()``. + On POSIX systems, this sends ``SIGKILL`` to the process. + + .. seealso:: :meth:`subprocess.Popen.kill` + """ + + @abstractmethod + def send_signal(self, signal: Signals) -> None: + """ + Send a signal to the subprocess. + + .. seealso:: :meth:`subprocess.Popen.send_signal` + + :param signal: the signal number (e.g. :data:`signal.SIGHUP`) + """ + + @property + @abstractmethod + def pid(self) -> int: + """The process ID of the process.""" + + @property + @abstractmethod + def returncode(self) -> int | None: + """ + The return code of the process. If the process has not yet terminated, this will + be ``None``. + """ + + @property + @abstractmethod + def stdin(self) -> ByteSendStream | None: + """The stream for the standard input of the process.""" + + @property + @abstractmethod + def stdout(self) -> ByteReceiveStream | None: + """The stream for the standard output of the process.""" + + @property + @abstractmethod + def stderr(self) -> ByteReceiveStream | None: + """The stream for the standard error output of the process.""" diff --git a/.venv/Lib/site-packages/anyio/abc/_tasks.py b/.venv/Lib/site-packages/anyio/abc/_tasks.py new file mode 100644 index 0000000..f6e5c40 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/abc/_tasks.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import sys +from abc import ABCMeta, abstractmethod +from collections.abc import Awaitable, Callable +from types import TracebackType +from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if TYPE_CHECKING: + from .._core._tasks import CancelScope + +T_Retval = TypeVar("T_Retval") +T_contra = TypeVar("T_contra", contravariant=True) +PosArgsT = TypeVarTuple("PosArgsT") + + +class TaskStatus(Protocol[T_contra]): + @overload + def started(self: TaskStatus[None]) -> None: ... + + @overload + def started(self, value: T_contra) -> None: ... + + def started(self, value: T_contra | None = None) -> None: + """ + Signal that the task has started. + + :param value: object passed back to the starter of the task + """ + + +class TaskGroup(metaclass=ABCMeta): + """ + Groups several asynchronous tasks together. + + :ivar cancel_scope: the cancel scope inherited by all child tasks + :vartype cancel_scope: CancelScope + + .. note:: On asyncio, support for eager task factories is considered to be + **experimental**. In particular, they don't follow the usual semantics of new + tasks being scheduled on the next iteration of the event loop, and may thus + cause unexpected behavior in code that wasn't written with such semantics in + mind. + """ + + cancel_scope: CancelScope + + @abstractmethod + def start_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[Any]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> None: + """ + Start a new task in this task group. + + :param func: a coroutine function + :param args: positional arguments to call the function with + :param name: name of the task, for the purposes of introspection and debugging + + .. versionadded:: 3.0 + """ + + @abstractmethod + async def start( + self, + func: Callable[..., Awaitable[Any]], + *args: object, + name: object = None, + ) -> Any: + """ + Start a new task and wait until it signals for readiness. + + :param func: a coroutine function + :param args: positional arguments to call the function with + :param name: name of the task, for the purposes of introspection and debugging + :return: the value passed to ``task_status.started()`` + :raises RuntimeError: if the task finishes without calling + ``task_status.started()`` + + .. versionadded:: 3.0 + """ + + @abstractmethod + async def __aenter__(self) -> TaskGroup: + """Enter the task group context and allow starting new tasks.""" + + @abstractmethod + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + """Exit the task group context waiting for all tasks to finish.""" diff --git a/.venv/Lib/site-packages/anyio/abc/_testing.py b/.venv/Lib/site-packages/anyio/abc/_testing.py new file mode 100644 index 0000000..7c50ed7 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/abc/_testing.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import types +from abc import ABCMeta, abstractmethod +from collections.abc import AsyncGenerator, Callable, Coroutine, Iterable +from typing import Any, TypeVar + +_T = TypeVar("_T") + + +class TestRunner(metaclass=ABCMeta): + """ + Encapsulates a running event loop. Every call made through this object will use the + same event loop. + """ + + def __enter__(self) -> TestRunner: + return self + + @abstractmethod + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> bool | None: ... + + @abstractmethod + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[_T, Any]], + kwargs: dict[str, Any], + ) -> Iterable[_T]: + """ + Run an async generator fixture. + + :param fixture_func: the fixture function + :param kwargs: keyword arguments to call the fixture function with + :return: an iterator yielding the value yielded from the async generator + """ + + @abstractmethod + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, _T]], + kwargs: dict[str, Any], + ) -> _T: + """ + Run an async fixture. + + :param fixture_func: the fixture function + :param kwargs: keyword arguments to call the fixture function with + :return: the return value of the fixture function + """ + + @abstractmethod + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + """ + Run an async test function. + + :param test_func: the test function + :param kwargs: keyword arguments to call the test function with + """ diff --git a/.venv/Lib/site-packages/anyio/from_thread.py b/.venv/Lib/site-packages/anyio/from_thread.py new file mode 100644 index 0000000..6179097 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/from_thread.py @@ -0,0 +1,527 @@ +from __future__ import annotations + +import sys +from collections.abc import Awaitable, Callable, Generator +from concurrent.futures import Future +from contextlib import ( + AbstractAsyncContextManager, + AbstractContextManager, + contextmanager, +) +from dataclasses import dataclass, field +from inspect import isawaitable +from threading import Lock, Thread, get_ident +from types import TracebackType +from typing import ( + Any, + Generic, + TypeVar, + cast, + overload, +) + +from ._core import _eventloop +from ._core._eventloop import get_async_backend, get_cancelled_exc_class, threadlocals +from ._core._synchronization import Event +from ._core._tasks import CancelScope, create_task_group +from .abc import AsyncBackend +from .abc._tasks import TaskStatus + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +PosArgsT = TypeVarTuple("PosArgsT") + + +def run( + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], *args: Unpack[PosArgsT] +) -> T_Retval: + """ + Call a coroutine function from a worker thread. + + :param func: a coroutine function + :param args: positional arguments for the callable + :return: the return value of the coroutine function + + """ + try: + async_backend = threadlocals.current_async_backend + token = threadlocals.current_token + except AttributeError: + raise RuntimeError( + "This function can only be run from an AnyIO worker thread" + ) from None + + return async_backend.run_async_from_thread(func, args, token=token) + + +def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] +) -> T_Retval: + """ + Call a function in the event loop thread from a worker thread. + + :param func: a callable + :param args: positional arguments for the callable + :return: the return value of the callable + + """ + try: + async_backend = threadlocals.current_async_backend + token = threadlocals.current_token + except AttributeError: + raise RuntimeError( + "This function can only be run from an AnyIO worker thread" + ) from None + + return async_backend.run_sync_from_thread(func, args, token=token) + + +class _BlockingAsyncContextManager(Generic[T_co], AbstractContextManager): + _enter_future: Future[T_co] + _exit_future: Future[bool | None] + _exit_event: Event + _exit_exc_info: tuple[ + type[BaseException] | None, BaseException | None, TracebackType | None + ] = (None, None, None) + + def __init__( + self, async_cm: AbstractAsyncContextManager[T_co], portal: BlockingPortal + ): + self._async_cm = async_cm + self._portal = portal + + async def run_async_cm(self) -> bool | None: + try: + self._exit_event = Event() + value = await self._async_cm.__aenter__() + except BaseException as exc: + self._enter_future.set_exception(exc) + raise + else: + self._enter_future.set_result(value) + + try: + # Wait for the sync context manager to exit. + # This next statement can raise `get_cancelled_exc_class()` if + # something went wrong in a task group in this async context + # manager. + await self._exit_event.wait() + finally: + # In case of cancellation, it could be that we end up here before + # `_BlockingAsyncContextManager.__exit__` is called, and an + # `_exit_exc_info` has been set. + result = await self._async_cm.__aexit__(*self._exit_exc_info) + return result + + def __enter__(self) -> T_co: + self._enter_future = Future() + self._exit_future = self._portal.start_task_soon(self.run_async_cm) + return self._enter_future.result() + + def __exit__( + self, + __exc_type: type[BaseException] | None, + __exc_value: BaseException | None, + __traceback: TracebackType | None, + ) -> bool | None: + self._exit_exc_info = __exc_type, __exc_value, __traceback + self._portal.call(self._exit_event.set) + return self._exit_future.result() + + +class _BlockingPortalTaskStatus(TaskStatus): + def __init__(self, future: Future): + self._future = future + + def started(self, value: object = None) -> None: + self._future.set_result(value) + + +class BlockingPortal: + """An object that lets external threads run code in an asynchronous event loop.""" + + def __new__(cls) -> BlockingPortal: + return get_async_backend().create_blocking_portal() + + def __init__(self) -> None: + self._event_loop_thread_id: int | None = get_ident() + self._stop_event = Event() + self._task_group = create_task_group() + self._cancelled_exc_class = get_cancelled_exc_class() + + async def __aenter__(self) -> BlockingPortal: + await self._task_group.__aenter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + await self.stop() + return await self._task_group.__aexit__(exc_type, exc_val, exc_tb) + + def _check_running(self) -> None: + if self._event_loop_thread_id is None: + raise RuntimeError("This portal is not running") + if self._event_loop_thread_id == get_ident(): + raise RuntimeError( + "This method cannot be called from the event loop thread" + ) + + async def sleep_until_stopped(self) -> None: + """Sleep until :meth:`stop` is called.""" + await self._stop_event.wait() + + async def stop(self, cancel_remaining: bool = False) -> None: + """ + Signal the portal to shut down. + + This marks the portal as no longer accepting new calls and exits from + :meth:`sleep_until_stopped`. + + :param cancel_remaining: ``True`` to cancel all the remaining tasks, ``False`` + to let them finish before returning + + """ + self._event_loop_thread_id = None + self._stop_event.set() + if cancel_remaining: + self._task_group.cancel_scope.cancel() + + async def _call_func( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + future: Future[T_Retval], + ) -> None: + def callback(f: Future[T_Retval]) -> None: + if f.cancelled() and self._event_loop_thread_id not in ( + None, + get_ident(), + ): + self.call(scope.cancel) + + try: + retval_or_awaitable = func(*args, **kwargs) + if isawaitable(retval_or_awaitable): + with CancelScope() as scope: + if future.cancelled(): + scope.cancel() + else: + future.add_done_callback(callback) + + retval = await retval_or_awaitable + else: + retval = retval_or_awaitable + except self._cancelled_exc_class: + future.cancel() + future.set_running_or_notify_cancel() + except BaseException as exc: + if not future.cancelled(): + future.set_exception(exc) + + # Let base exceptions fall through + if not isinstance(exc, Exception): + raise + else: + if not future.cancelled(): + future.set_result(retval) + finally: + scope = None # type: ignore[assignment] + + def _spawn_task_from_thread( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + name: object, + future: Future[T_Retval], + ) -> None: + """ + Spawn a new task using the given callable. + + Implementers must ensure that the future is resolved when the task finishes. + + :param func: a callable + :param args: positional arguments to be passed to the callable + :param kwargs: keyword arguments to be passed to the callable + :param name: name of the task (will be coerced to a string if not ``None``) + :param future: a future that will resolve to the return value of the callable, + or the exception raised during its execution + + """ + raise NotImplementedError + + @overload + def call( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + ) -> T_Retval: ... + + @overload + def call( + self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] + ) -> T_Retval: ... + + def call( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + *args: Unpack[PosArgsT], + ) -> T_Retval: + """ + Call the given function in the event loop thread. + + If the callable returns a coroutine object, it is awaited on. + + :param func: any callable + :raises RuntimeError: if the portal is not running or if this method is called + from within the event loop thread + + """ + return cast(T_Retval, self.start_task_soon(func, *args).result()) + + @overload + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: ... + + @overload + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: ... + + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: + """ + Start a task in the portal's task group. + + The task will be run inside a cancel scope which can be cancelled by cancelling + the returned future. + + :param func: the target function + :param args: positional arguments passed to ``func`` + :param name: name of the task (will be coerced to a string if not ``None``) + :return: a future that resolves with the return value of the callable if the + task completes successfully, or with the exception raised in the task + :raises RuntimeError: if the portal is not running or if this method is called + from within the event loop thread + :rtype: concurrent.futures.Future[T_Retval] + + .. versionadded:: 3.0 + + """ + self._check_running() + f: Future[T_Retval] = Future() + self._spawn_task_from_thread(func, args, {}, name, f) + return f + + def start_task( + self, + func: Callable[..., Awaitable[T_Retval]], + *args: object, + name: object = None, + ) -> tuple[Future[T_Retval], Any]: + """ + Start a task in the portal's task group and wait until it signals for readiness. + + This method works the same way as :meth:`.abc.TaskGroup.start`. + + :param func: the target function + :param args: positional arguments passed to ``func`` + :param name: name of the task (will be coerced to a string if not ``None``) + :return: a tuple of (future, task_status_value) where the ``task_status_value`` + is the value passed to ``task_status.started()`` from within the target + function + :rtype: tuple[concurrent.futures.Future[T_Retval], Any] + + .. versionadded:: 3.0 + + """ + + def task_done(future: Future[T_Retval]) -> None: + if not task_status_future.done(): + if future.cancelled(): + task_status_future.cancel() + elif future.exception(): + task_status_future.set_exception(future.exception()) + else: + exc = RuntimeError( + "Task exited without calling task_status.started()" + ) + task_status_future.set_exception(exc) + + self._check_running() + task_status_future: Future = Future() + task_status = _BlockingPortalTaskStatus(task_status_future) + f: Future = Future() + f.add_done_callback(task_done) + self._spawn_task_from_thread(func, args, {"task_status": task_status}, name, f) + return f, task_status_future.result() + + def wrap_async_context_manager( + self, cm: AbstractAsyncContextManager[T_co] + ) -> AbstractContextManager[T_co]: + """ + Wrap an async context manager as a synchronous context manager via this portal. + + Spawns a task that will call both ``__aenter__()`` and ``__aexit__()``, stopping + in the middle until the synchronous context manager exits. + + :param cm: an asynchronous context manager + :return: a synchronous context manager + + .. versionadded:: 2.1 + + """ + return _BlockingAsyncContextManager(cm, self) + + +@dataclass +class BlockingPortalProvider: + """ + A manager for a blocking portal. Used as a context manager. The first thread to + enter this context manager causes a blocking portal to be started with the specific + parameters, and the last thread to exit causes the portal to be shut down. Thus, + there will be exactly one blocking portal running in this context as long as at + least one thread has entered this context manager. + + The parameters are the same as for :func:`~anyio.run`. + + :param backend: name of the backend + :param backend_options: backend options + + .. versionadded:: 4.4 + """ + + backend: str = "asyncio" + backend_options: dict[str, Any] | None = None + _lock: Lock = field(init=False, default_factory=Lock) + _leases: int = field(init=False, default=0) + _portal: BlockingPortal = field(init=False) + _portal_cm: AbstractContextManager[BlockingPortal] | None = field( + init=False, default=None + ) + + def __enter__(self) -> BlockingPortal: + with self._lock: + if self._portal_cm is None: + self._portal_cm = start_blocking_portal( + self.backend, self.backend_options + ) + self._portal = self._portal_cm.__enter__() + + self._leases += 1 + return self._portal + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + portal_cm: AbstractContextManager[BlockingPortal] | None = None + with self._lock: + assert self._portal_cm + assert self._leases > 0 + self._leases -= 1 + if not self._leases: + portal_cm = self._portal_cm + self._portal_cm = None + del self._portal + + if portal_cm: + portal_cm.__exit__(None, None, None) + + +@contextmanager +def start_blocking_portal( + backend: str = "asyncio", backend_options: dict[str, Any] | None = None +) -> Generator[BlockingPortal, Any, None]: + """ + Start a new event loop in a new thread and run a blocking portal in its main task. + + The parameters are the same as for :func:`~anyio.run`. + + :param backend: name of the backend + :param backend_options: backend options + :return: a context manager that yields a blocking portal + + .. versionchanged:: 3.0 + Usage as a context manager is now required. + + """ + + async def run_portal() -> None: + async with BlockingPortal() as portal_: + future.set_result(portal_) + await portal_.sleep_until_stopped() + + def run_blocking_portal() -> None: + if future.set_running_or_notify_cancel(): + try: + _eventloop.run( + run_portal, backend=backend, backend_options=backend_options + ) + except BaseException as exc: + if not future.done(): + future.set_exception(exc) + + future: Future[BlockingPortal] = Future() + thread = Thread(target=run_blocking_portal, daemon=True) + thread.start() + try: + cancel_remaining_tasks = False + portal = future.result() + try: + yield portal + except BaseException: + cancel_remaining_tasks = True + raise + finally: + try: + portal.call(portal.stop, cancel_remaining_tasks) + except RuntimeError: + pass + finally: + thread.join() + + +def check_cancelled() -> None: + """ + Check if the cancel scope of the host task's running the current worker thread has + been cancelled. + + If the host task's current cancel scope has indeed been cancelled, the + backend-specific cancellation exception will be raised. + + :raises RuntimeError: if the current thread was not spawned by + :func:`.to_thread.run_sync` + + """ + try: + async_backend: AsyncBackend = threadlocals.current_async_backend + except AttributeError: + raise RuntimeError( + "This function can only be run from an AnyIO worker thread" + ) from None + + async_backend.check_cancelled() diff --git a/.venv/Lib/site-packages/anyio/lowlevel.py b/.venv/Lib/site-packages/anyio/lowlevel.py new file mode 100644 index 0000000..14c7668 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/lowlevel.py @@ -0,0 +1,161 @@ +from __future__ import annotations + +import enum +from dataclasses import dataclass +from typing import Any, Generic, Literal, TypeVar, overload +from weakref import WeakKeyDictionary + +from ._core._eventloop import get_async_backend + +T = TypeVar("T") +D = TypeVar("D") + + +async def checkpoint() -> None: + """ + Check for cancellation and allow the scheduler to switch to another task. + + Equivalent to (but more efficient than):: + + await checkpoint_if_cancelled() + await cancel_shielded_checkpoint() + + + .. versionadded:: 3.0 + + """ + await get_async_backend().checkpoint() + + +async def checkpoint_if_cancelled() -> None: + """ + Enter a checkpoint if the enclosing cancel scope has been cancelled. + + This does not allow the scheduler to switch to a different task. + + .. versionadded:: 3.0 + + """ + await get_async_backend().checkpoint_if_cancelled() + + +async def cancel_shielded_checkpoint() -> None: + """ + Allow the scheduler to switch to another task but without checking for cancellation. + + Equivalent to (but potentially more efficient than):: + + with CancelScope(shield=True): + await checkpoint() + + + .. versionadded:: 3.0 + + """ + await get_async_backend().cancel_shielded_checkpoint() + + +def current_token() -> object: + """ + Return a backend specific token object that can be used to get back to the event + loop. + + """ + return get_async_backend().current_token() + + +_run_vars: WeakKeyDictionary[Any, dict[str, Any]] = WeakKeyDictionary() +_token_wrappers: dict[Any, _TokenWrapper] = {} + + +@dataclass(frozen=True) +class _TokenWrapper: + __slots__ = "_token", "__weakref__" + _token: object + + +class _NoValueSet(enum.Enum): + NO_VALUE_SET = enum.auto() + + +class RunvarToken(Generic[T]): + __slots__ = "_var", "_value", "_redeemed" + + def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]): + self._var = var + self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value + self._redeemed = False + + +class RunVar(Generic[T]): + """ + Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop. + """ + + __slots__ = "_name", "_default" + + NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET + + _token_wrappers: set[_TokenWrapper] = set() + + def __init__( + self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET + ): + self._name = name + self._default = default + + @property + def _current_vars(self) -> dict[str, T]: + token = current_token() + try: + return _run_vars[token] + except KeyError: + run_vars = _run_vars[token] = {} + return run_vars + + @overload + def get(self, default: D) -> T | D: ... + + @overload + def get(self) -> T: ... + + def get( + self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET + ) -> T | D: + try: + return self._current_vars[self._name] + except KeyError: + if default is not RunVar.NO_VALUE_SET: + return default + elif self._default is not RunVar.NO_VALUE_SET: + return self._default + + raise LookupError( + f'Run variable "{self._name}" has no value and no default set' + ) + + def set(self, value: T) -> RunvarToken[T]: + current_vars = self._current_vars + token = RunvarToken(self, current_vars.get(self._name, RunVar.NO_VALUE_SET)) + current_vars[self._name] = value + return token + + def reset(self, token: RunvarToken[T]) -> None: + if token._var is not self: + raise ValueError("This token does not belong to this RunVar") + + if token._redeemed: + raise ValueError("This token has already been used") + + if token._value is _NoValueSet.NO_VALUE_SET: + try: + del self._current_vars[self._name] + except KeyError: + pass + else: + self._current_vars[self._name] = token._value + + token._redeemed = True + + def __repr__(self) -> str: + return f"" diff --git a/.venv/Lib/site-packages/anyio/py.typed b/.venv/Lib/site-packages/anyio/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/anyio/pytest_plugin.py b/.venv/Lib/site-packages/anyio/pytest_plugin.py new file mode 100644 index 0000000..21e4ab2 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/pytest_plugin.py @@ -0,0 +1,272 @@ +from __future__ import annotations + +import socket +import sys +from collections.abc import Callable, Generator, Iterator +from contextlib import ExitStack, contextmanager +from inspect import isasyncgenfunction, iscoroutinefunction, ismethod +from typing import Any, cast + +import pytest +import sniffio +from _pytest.fixtures import SubRequest +from _pytest.outcomes import Exit + +from ._core._eventloop import get_all_backends, get_async_backend +from ._core._exceptions import iterate_exceptions +from .abc import TestRunner + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + +_current_runner: TestRunner | None = None +_runner_stack: ExitStack | None = None +_runner_leases = 0 + + +def extract_backend_and_options(backend: object) -> tuple[str, dict[str, Any]]: + if isinstance(backend, str): + return backend, {} + elif isinstance(backend, tuple) and len(backend) == 2: + if isinstance(backend[0], str) and isinstance(backend[1], dict): + return cast(tuple[str, dict[str, Any]], backend) + + raise TypeError("anyio_backend must be either a string or tuple of (string, dict)") + + +@contextmanager +def get_runner( + backend_name: str, backend_options: dict[str, Any] +) -> Iterator[TestRunner]: + global _current_runner, _runner_leases, _runner_stack + if _current_runner is None: + asynclib = get_async_backend(backend_name) + _runner_stack = ExitStack() + if sniffio.current_async_library_cvar.get(None) is None: + # Since we're in control of the event loop, we can cache the name of the + # async library + token = sniffio.current_async_library_cvar.set(backend_name) + _runner_stack.callback(sniffio.current_async_library_cvar.reset, token) + + backend_options = backend_options or {} + _current_runner = _runner_stack.enter_context( + asynclib.create_test_runner(backend_options) + ) + + _runner_leases += 1 + try: + yield _current_runner + finally: + _runner_leases -= 1 + if not _runner_leases: + assert _runner_stack is not None + _runner_stack.close() + _runner_stack = _current_runner = None + + +def pytest_configure(config: Any) -> None: + config.addinivalue_line( + "markers", + "anyio: mark the (coroutine function) test to be run asynchronously via anyio.", + ) + + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup(fixturedef: Any, request: Any) -> Generator[Any]: + def wrapper( + *args: Any, anyio_backend: Any, request: SubRequest, **kwargs: Any + ) -> Any: + # Rebind any fixture methods to the request instance + if ( + request.instance + and ismethod(func) + and type(func.__self__) is type(request.instance) + ): + local_func = func.__func__.__get__(request.instance) + else: + local_func = func + + backend_name, backend_options = extract_backend_and_options(anyio_backend) + if has_backend_arg: + kwargs["anyio_backend"] = anyio_backend + + if has_request_arg: + kwargs["request"] = request + + with get_runner(backend_name, backend_options) as runner: + if isasyncgenfunction(local_func): + yield from runner.run_asyncgen_fixture(local_func, kwargs) + else: + yield runner.run_fixture(local_func, kwargs) + + # Only apply this to coroutine functions and async generator functions in requests + # that involve the anyio_backend fixture + func = fixturedef.func + if isasyncgenfunction(func) or iscoroutinefunction(func): + if "anyio_backend" in request.fixturenames: + fixturedef.func = wrapper + original_argname = fixturedef.argnames + + if not (has_backend_arg := "anyio_backend" in fixturedef.argnames): + fixturedef.argnames += ("anyio_backend",) + + if not (has_request_arg := "request" in fixturedef.argnames): + fixturedef.argnames += ("request",) + + try: + return (yield) + finally: + fixturedef.func = func + fixturedef.argnames = original_argname + + return (yield) + + +@pytest.hookimpl(tryfirst=True) +def pytest_pycollect_makeitem(collector: Any, name: Any, obj: Any) -> None: + if collector.istestfunction(obj, name): + inner_func = obj.hypothesis.inner_test if hasattr(obj, "hypothesis") else obj + if iscoroutinefunction(inner_func): + marker = collector.get_closest_marker("anyio") + own_markers = getattr(obj, "pytestmark", ()) + if marker or any(marker.name == "anyio" for marker in own_markers): + pytest.mark.usefixtures("anyio_backend")(obj) + + +@pytest.hookimpl(tryfirst=True) +def pytest_pyfunc_call(pyfuncitem: Any) -> bool | None: + def run_with_hypothesis(**kwargs: Any) -> None: + with get_runner(backend_name, backend_options) as runner: + runner.run_test(original_func, kwargs) + + backend = pyfuncitem.funcargs.get("anyio_backend") + if backend: + backend_name, backend_options = extract_backend_and_options(backend) + + if hasattr(pyfuncitem.obj, "hypothesis"): + # Wrap the inner test function unless it's already wrapped + original_func = pyfuncitem.obj.hypothesis.inner_test + if original_func.__qualname__ != run_with_hypothesis.__qualname__: + if iscoroutinefunction(original_func): + pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis + + return None + + if iscoroutinefunction(pyfuncitem.obj): + funcargs = pyfuncitem.funcargs + testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} + with get_runner(backend_name, backend_options) as runner: + try: + runner.run_test(pyfuncitem.obj, testargs) + except ExceptionGroup as excgrp: + for exc in iterate_exceptions(excgrp): + if isinstance(exc, (Exit, KeyboardInterrupt, SystemExit)): + raise exc from excgrp + + raise + + return True + + return None + + +@pytest.fixture(scope="module", params=get_all_backends()) +def anyio_backend(request: Any) -> Any: + return request.param + + +@pytest.fixture +def anyio_backend_name(anyio_backend: Any) -> str: + if isinstance(anyio_backend, str): + return anyio_backend + else: + return anyio_backend[0] + + +@pytest.fixture +def anyio_backend_options(anyio_backend: Any) -> dict[str, Any]: + if isinstance(anyio_backend, str): + return {} + else: + return anyio_backend[1] + + +class FreePortFactory: + """ + Manages port generation based on specified socket kind, ensuring no duplicate + ports are generated. + + This class provides functionality for generating available free ports on the + system. It is initialized with a specific socket kind and can generate ports + for given address families while avoiding reuse of previously generated ports. + + Users should not instantiate this class directly, but use the + ``free_tcp_port_factory`` and ``free_udp_port_factory`` fixtures instead. For simple + uses cases, ``free_tcp_port`` and ``free_udp_port`` can be used instead. + """ + + def __init__(self, kind: socket.SocketKind) -> None: + self._kind = kind + self._generated = set[int]() + + @property + def kind(self) -> socket.SocketKind: + """ + The type of socket connection (e.g., :data:`~socket.SOCK_STREAM` or + :data:`~socket.SOCK_DGRAM`) used to bind for checking port availability + + """ + return self._kind + + def __call__(self, family: socket.AddressFamily | None = None) -> int: + """ + Return an unbound port for the given address family. + + :param family: if omitted, both IPv4 and IPv6 addresses will be tried + :return: a port number + + """ + if family is not None: + families = [family] + else: + families = [socket.AF_INET] + if socket.has_ipv6: + families.append(socket.AF_INET6) + + while True: + port = 0 + with ExitStack() as stack: + for family in families: + sock = stack.enter_context(socket.socket(family, self._kind)) + addr = "::1" if family == socket.AF_INET6 else "127.0.0.1" + try: + sock.bind((addr, port)) + except OSError: + break + + if not port: + port = sock.getsockname()[1] + else: + if port not in self._generated: + self._generated.add(port) + return port + + +@pytest.fixture(scope="session") +def free_tcp_port_factory() -> FreePortFactory: + return FreePortFactory(socket.SOCK_STREAM) + + +@pytest.fixture(scope="session") +def free_udp_port_factory() -> FreePortFactory: + return FreePortFactory(socket.SOCK_DGRAM) + + +@pytest.fixture +def free_tcp_port(free_tcp_port_factory: Callable[[], int]) -> int: + return free_tcp_port_factory() + + +@pytest.fixture +def free_udp_port(free_udp_port_factory: Callable[[], int]) -> int: + return free_udp_port_factory() diff --git a/.venv/Lib/site-packages/anyio/streams/__init__.py b/.venv/Lib/site-packages/anyio/streams/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/anyio/streams/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/streams/__pycache__/__init__.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..276d3f0caa9f47e57628144cc911ab190e3ca74b GIT binary patch literal 210 zcmX@j%ge<81S^xRGC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!DonPD2`x@7Dvk+G z%#CqLEzT~paZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%IiB<59S=EoG56s0ES7RSWLXXa&=#K-FuRNmsS e$<0qG%}KQ@VgpaZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%IiB<59S=Ia-i6s0ES7VF2yXXa&=#K-FuRNmsS e$<0qG%}KQ@VgpCw0 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc b/.venv/Lib/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e132bce018c7f82e4d99f43be1b559b5a11c0e8 GIT binary patch literal 6139 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/streams/__pycache__/memory.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/streams/__pycache__/memory.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43f9967d50219c9a40d68a668245d7dbd9f0893e GIT binary patch literal 15074 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

>^=O5*QeQ%Sr8eM%BUz@}ctTXSjd>vJnj zZ43L}_^e?7_1$sx-Kt8tp7_w!x8m_#diCO~8PBGaXVa}ccRWwtYhAy%_1*37Y|nJ; zN_FhYbPT0BhSDAT)2#H2|8x`_!t9@iwpJeB{U1 z&;UXlQ#F8qyEO3ySw_h5zzkz3PB_PLs6mQPtX193*#czk`2v-u*7zp0&Z5qw8V%i4 z$3@3CT))ZU8M;tiQz)yESkRqp_9;k|JEo7e%a+*L(+1lKPl=sSW?TI7%gM3TiKLstPZR0B4t7ctrjc8!3!i|9K1dtg;X3;>`(vOh6 zD$YlSq4JWQoQKD3BXZaAy`V|6n8oWd$x(XDNqcBBkM3i&yRK3O?HVlEblyB?E`_H& z3Pq6`=GZxNutKK*jOSWCoqOgPPUgp_ny_yW8C5I?y;PtD3H@N9K8ciW*9cDKA#SsB=RtFVx9{B-0-3boFPo1#iU+h zejZa>AW|A&c0t6R@%tmdCc->mOnl{{hh&96IMh-SKSFplNsYrOAtv8F$Yech8Ey``yWR zCNut_lz%AWA5Qs)lb?Gr=^sw}kHN+9!jWs+R(wr5(7Y?<+qLZ5y~1~1w*PhIink@> zZBKdIm%SY;-nvyM1OA%YbK2eW(9O6!3$kjkx2HVqY3KUIv3HNZb3C(QcWT4#JI>vP z)0dBWf9UG@xVky%(mb|&fs6*_YbcQMlI{_ZHT!swb-~eP0k*C$f~{|qgsn{=`bvIq zQ0jAoIg~}IUpet$+*b4ww&#?Nq=t~1g_^q-+5>6}nqia0R!w4PbUwok$ z=!HB0PY_Z`2>eO>I8)-M<8ma5l|eW_Vd&)$+)yDIb%c_a1tXrw$uO_Y}(sdg%-i>#>8v(HA zf617jKu7<3Qr@17cOc~*xaD|n-?Delinn&v0yRDXN5R_&j@|LMj%Ql7rQpA3+e%Ht z)!{3{nVNxA4KzsBY`<3*xDLqwmInYzJ+oR1H2s!-zyrX4I{^P(2>iFRZ&r0P@0uX- z?cSkQ^9S|5J)2D*bhCT8PcU~ax|tN z&y4+)FuyDR1ebyNaRb+$l zTlg(C(5<>({eWx4MHW_9;-P~Q;Qof|V}E~q>G-nenWX)hwO9`N31(E7ap_UVMIl!+cS%4ZPzk;Vq&8*Fe?@;-H76$_r2ud) zhDZU}ZhVZO;_M^_^$H{^3B#dLOza<=wLEeex$QJw@<)FCy<-F#3bT?_>&Q zw+0PBFsSHzx7=Y;VfS`e8bYu!UPKfKN;Ea;X?Y3~yC<-~&pV44;x-oN5t}DQWnxfLFxe{=c`b2?d;J&rMt5TiE_L1r-Dn9 z*KiJhj!`imAf!3lTE@)LAm*oW8NPw7kqJh~UqTG}1b>G>5XVsAUkbOXP;j}8&cp78 zvE60t=&=x5YU90yZ9Y9hOSK(F{|bKwLfe<-`br|Sy;*CMr6oJqY}uOKQE3^>wt4`e zUH=C_Xpx4%2K?pE#5DZi0e<;IfQ1;c0FmMhC!$fX>hZ^B1R0;;#|?_p_;r~m5j<%W zyH+d+nM(w@P_aT9EDKVBrHIiHCuVD40s3t8V;E{8D{lD8mWcg9S%hCkK_hg`=&vgj zbLeDP{oDq>>mdlBDfVdMY!oDaRJF7b1|x_>soReDllpDWE=(Q4h%R(UzY4p zf9bS~+=M(lwvrQK_~e9Tf6A=?Ipg_=>Hdi6`YF@)5i^it27bj^SW}jP=vOX=-S!c) zE5+>kDbxP53h(8HbVcL5^{&mmaAvV)+2)_;?mDY44_+Tzc0Mt0e`u{>+ZG2OGVplN zz?hs1;xb$F&}3(ivDeNno_XslSq9TtGh?b zSBJ2yd(n2Cd#5rvu=o9~sewaT2J^Jqn0x?Y*%{mD(c-)~55dN#{o NCT;M*gh{$s{{y{q$+iFh literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/streams/__pycache__/stapled.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/streams/__pycache__/stapled.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4568af1f7866bf4390f85497fe61313445d34069 GIT binary patch literal 7635 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc b/.venv/Lib/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e6ac8a1dda110efe16ee3b543c25a2de295e51b GIT binary patch literal 7521 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/streams/__pycache__/text.cpython-312.pyc b/.venv/Lib/site-packages/anyio/streams/__pycache__/text.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61489f3c47022f45bbe9c39d10683b77fe9659d8 GIT binary patch literal 8315 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/streams/__pycache__/tls.cpython-312-pytest-8.3.5.pyc b/.venv/Lib/site-packages/anyio/streams/__pycache__/tls.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa4a444016a674a98d72d090b0c54c8c7078d167 GIT binary patch literal 17693 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc b/.venv/Lib/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d3426413891ac0f7eb9ca82fe50cde0db317063 GIT binary patch literal 17580 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 PB85K)-~$V0$prjgkK?Z$ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/anyio/streams/buffered.py b/.venv/Lib/site-packages/anyio/streams/buffered.py new file mode 100644 index 0000000..f5d5e83 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/streams/buffered.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +from collections.abc import Callable, Mapping +from dataclasses import dataclass, field +from typing import Any + +from .. import ClosedResourceError, DelimiterNotFound, EndOfStream, IncompleteRead +from ..abc import AnyByteReceiveStream, ByteReceiveStream + + +@dataclass(eq=False) +class BufferedByteReceiveStream(ByteReceiveStream): + """ + Wraps any bytes-based receive stream and uses a buffer to provide sophisticated + receiving capabilities in the form of a byte stream. + """ + + receive_stream: AnyByteReceiveStream + _buffer: bytearray = field(init=False, default_factory=bytearray) + _closed: bool = field(init=False, default=False) + + async def aclose(self) -> None: + await self.receive_stream.aclose() + self._closed = True + + @property + def buffer(self) -> bytes: + """The bytes currently in the buffer.""" + return bytes(self._buffer) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.receive_stream.extra_attributes + + async def receive(self, max_bytes: int = 65536) -> bytes: + if self._closed: + raise ClosedResourceError + + if self._buffer: + chunk = bytes(self._buffer[:max_bytes]) + del self._buffer[:max_bytes] + return chunk + elif isinstance(self.receive_stream, ByteReceiveStream): + return await self.receive_stream.receive(max_bytes) + else: + # With a bytes-oriented object stream, we need to handle any surplus bytes + # we get from the receive() call + chunk = await self.receive_stream.receive() + if len(chunk) > max_bytes: + # Save the surplus bytes in the buffer + self._buffer.extend(chunk[max_bytes:]) + return chunk[:max_bytes] + else: + return chunk + + async def receive_exactly(self, nbytes: int) -> bytes: + """ + Read exactly the given amount of bytes from the stream. + + :param nbytes: the number of bytes to read + :return: the bytes read + :raises ~anyio.IncompleteRead: if the stream was closed before the requested + amount of bytes could be read from the stream + + """ + while True: + remaining = nbytes - len(self._buffer) + if remaining <= 0: + retval = self._buffer[:nbytes] + del self._buffer[:nbytes] + return bytes(retval) + + try: + if isinstance(self.receive_stream, ByteReceiveStream): + chunk = await self.receive_stream.receive(remaining) + else: + chunk = await self.receive_stream.receive() + except EndOfStream as exc: + raise IncompleteRead from exc + + self._buffer.extend(chunk) + + async def receive_until(self, delimiter: bytes, max_bytes: int) -> bytes: + """ + Read from the stream until the delimiter is found or max_bytes have been read. + + :param delimiter: the marker to look for in the stream + :param max_bytes: maximum number of bytes that will be read before raising + :exc:`~anyio.DelimiterNotFound` + :return: the bytes read (not including the delimiter) + :raises ~anyio.IncompleteRead: if the stream was closed before the delimiter + was found + :raises ~anyio.DelimiterNotFound: if the delimiter is not found within the + bytes read up to the maximum allowed + + """ + delimiter_size = len(delimiter) + offset = 0 + while True: + # Check if the delimiter can be found in the current buffer + index = self._buffer.find(delimiter, offset) + if index >= 0: + found = self._buffer[:index] + del self._buffer[: index + len(delimiter) :] + return bytes(found) + + # Check if the buffer is already at or over the limit + if len(self._buffer) >= max_bytes: + raise DelimiterNotFound(max_bytes) + + # Read more data into the buffer from the socket + try: + data = await self.receive_stream.receive() + except EndOfStream as exc: + raise IncompleteRead from exc + + # Move the offset forward and add the new data to the buffer + offset = max(len(self._buffer) - delimiter_size + 1, 0) + self._buffer.extend(data) diff --git a/.venv/Lib/site-packages/anyio/streams/file.py b/.venv/Lib/site-packages/anyio/streams/file.py new file mode 100644 index 0000000..f492464 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/streams/file.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +from collections.abc import Callable, Mapping +from io import SEEK_SET, UnsupportedOperation +from os import PathLike +from pathlib import Path +from typing import Any, BinaryIO, cast + +from .. import ( + BrokenResourceError, + ClosedResourceError, + EndOfStream, + TypedAttributeSet, + to_thread, + typed_attribute, +) +from ..abc import ByteReceiveStream, ByteSendStream + + +class FileStreamAttribute(TypedAttributeSet): + #: the open file descriptor + file: BinaryIO = typed_attribute() + #: the path of the file on the file system, if available (file must be a real file) + path: Path = typed_attribute() + #: the file number, if available (file must be a real file or a TTY) + fileno: int = typed_attribute() + + +class _BaseFileStream: + def __init__(self, file: BinaryIO): + self._file = file + + async def aclose(self) -> None: + await to_thread.run_sync(self._file.close) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + attributes: dict[Any, Callable[[], Any]] = { + FileStreamAttribute.file: lambda: self._file, + } + + if hasattr(self._file, "name"): + attributes[FileStreamAttribute.path] = lambda: Path(self._file.name) + + try: + self._file.fileno() + except UnsupportedOperation: + pass + else: + attributes[FileStreamAttribute.fileno] = lambda: self._file.fileno() + + return attributes + + +class FileReadStream(_BaseFileStream, ByteReceiveStream): + """ + A byte stream that reads from a file in the file system. + + :param file: a file that has been opened for reading in binary mode + + .. versionadded:: 3.0 + """ + + @classmethod + async def from_path(cls, path: str | PathLike[str]) -> FileReadStream: + """ + Create a file read stream by opening the given file. + + :param path: path of the file to read from + + """ + file = await to_thread.run_sync(Path(path).open, "rb") + return cls(cast(BinaryIO, file)) + + async def receive(self, max_bytes: int = 65536) -> bytes: + try: + data = await to_thread.run_sync(self._file.read, max_bytes) + except ValueError: + raise ClosedResourceError from None + except OSError as exc: + raise BrokenResourceError from exc + + if data: + return data + else: + raise EndOfStream + + async def seek(self, position: int, whence: int = SEEK_SET) -> int: + """ + Seek the file to the given position. + + .. seealso:: :meth:`io.IOBase.seek` + + .. note:: Not all file descriptors are seekable. + + :param position: position to seek the file to + :param whence: controls how ``position`` is interpreted + :return: the new absolute position + :raises OSError: if the file is not seekable + + """ + return await to_thread.run_sync(self._file.seek, position, whence) + + async def tell(self) -> int: + """ + Return the current stream position. + + .. note:: Not all file descriptors are seekable. + + :return: the current absolute position + :raises OSError: if the file is not seekable + + """ + return await to_thread.run_sync(self._file.tell) + + +class FileWriteStream(_BaseFileStream, ByteSendStream): + """ + A byte stream that writes to a file in the file system. + + :param file: a file that has been opened for writing in binary mode + + .. versionadded:: 3.0 + """ + + @classmethod + async def from_path( + cls, path: str | PathLike[str], append: bool = False + ) -> FileWriteStream: + """ + Create a file write stream by opening the given file for writing. + + :param path: path of the file to write to + :param append: if ``True``, open the file for appending; if ``False``, any + existing file at the given path will be truncated + + """ + mode = "ab" if append else "wb" + file = await to_thread.run_sync(Path(path).open, mode) + return cls(cast(BinaryIO, file)) + + async def send(self, item: bytes) -> None: + try: + await to_thread.run_sync(self._file.write, item) + except ValueError: + raise ClosedResourceError from None + except OSError as exc: + raise BrokenResourceError from exc diff --git a/.venv/Lib/site-packages/anyio/streams/memory.py b/.venv/Lib/site-packages/anyio/streams/memory.py new file mode 100644 index 0000000..83bf1d9 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/streams/memory.py @@ -0,0 +1,317 @@ +from __future__ import annotations + +import warnings +from collections import OrderedDict, deque +from dataclasses import dataclass, field +from types import TracebackType +from typing import Generic, NamedTuple, TypeVar + +from .. import ( + BrokenResourceError, + ClosedResourceError, + EndOfStream, + WouldBlock, +) +from .._core._testing import TaskInfo, get_current_task +from ..abc import Event, ObjectReceiveStream, ObjectSendStream +from ..lowlevel import checkpoint + +T_Item = TypeVar("T_Item") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class MemoryObjectStreamStatistics(NamedTuple): + current_buffer_used: int #: number of items stored in the buffer + #: maximum number of items that can be stored on this stream (or :data:`math.inf`) + max_buffer_size: float + open_send_streams: int #: number of unclosed clones of the send stream + open_receive_streams: int #: number of unclosed clones of the receive stream + #: number of tasks blocked on :meth:`MemoryObjectSendStream.send` + tasks_waiting_send: int + #: number of tasks blocked on :meth:`MemoryObjectReceiveStream.receive` + tasks_waiting_receive: int + + +@dataclass(eq=False) +class MemoryObjectItemReceiver(Generic[T_Item]): + task_info: TaskInfo = field(init=False, default_factory=get_current_task) + item: T_Item = field(init=False) + + def __repr__(self) -> str: + # When item is not defined, we get following error with default __repr__: + # AttributeError: 'MemoryObjectItemReceiver' object has no attribute 'item' + item = getattr(self, "item", None) + return f"{self.__class__.__name__}(task_info={self.task_info}, item={item!r})" + + +@dataclass(eq=False) +class MemoryObjectStreamState(Generic[T_Item]): + max_buffer_size: float = field() + buffer: deque[T_Item] = field(init=False, default_factory=deque) + open_send_channels: int = field(init=False, default=0) + open_receive_channels: int = field(init=False, default=0) + waiting_receivers: OrderedDict[Event, MemoryObjectItemReceiver[T_Item]] = field( + init=False, default_factory=OrderedDict + ) + waiting_senders: OrderedDict[Event, T_Item] = field( + init=False, default_factory=OrderedDict + ) + + def statistics(self) -> MemoryObjectStreamStatistics: + return MemoryObjectStreamStatistics( + len(self.buffer), + self.max_buffer_size, + self.open_send_channels, + self.open_receive_channels, + len(self.waiting_senders), + len(self.waiting_receivers), + ) + + +@dataclass(eq=False) +class MemoryObjectReceiveStream(Generic[T_co], ObjectReceiveStream[T_co]): + _state: MemoryObjectStreamState[T_co] + _closed: bool = field(init=False, default=False) + + def __post_init__(self) -> None: + self._state.open_receive_channels += 1 + + def receive_nowait(self) -> T_co: + """ + Receive the next item if it can be done without waiting. + + :return: the received item + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.EndOfStream: if the buffer is empty and this stream has been + closed from the sending end + :raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks + waiting to send + + """ + if self._closed: + raise ClosedResourceError + + if self._state.waiting_senders: + # Get the item from the next sender + send_event, item = self._state.waiting_senders.popitem(last=False) + self._state.buffer.append(item) + send_event.set() + + if self._state.buffer: + return self._state.buffer.popleft() + elif not self._state.open_send_channels: + raise EndOfStream + + raise WouldBlock + + async def receive(self) -> T_co: + await checkpoint() + try: + return self.receive_nowait() + except WouldBlock: + # Add ourselves in the queue + receive_event = Event() + receiver = MemoryObjectItemReceiver[T_co]() + self._state.waiting_receivers[receive_event] = receiver + + try: + await receive_event.wait() + finally: + self._state.waiting_receivers.pop(receive_event, None) + + try: + return receiver.item + except AttributeError: + raise EndOfStream from None + + def clone(self) -> MemoryObjectReceiveStream[T_co]: + """ + Create a clone of this receive stream. + + Each clone can be closed separately. Only when all clones have been closed will + the receiving end of the memory stream be considered closed by the sending ends. + + :return: the cloned stream + + """ + if self._closed: + raise ClosedResourceError + + return MemoryObjectReceiveStream(_state=self._state) + + def close(self) -> None: + """ + Close the stream. + + This works the exact same way as :meth:`aclose`, but is provided as a special + case for the benefit of synchronous callbacks. + + """ + if not self._closed: + self._closed = True + self._state.open_receive_channels -= 1 + if self._state.open_receive_channels == 0: + send_events = list(self._state.waiting_senders.keys()) + for event in send_events: + event.set() + + async def aclose(self) -> None: + self.close() + + def statistics(self) -> MemoryObjectStreamStatistics: + """ + Return statistics about the current state of this stream. + + .. versionadded:: 3.0 + """ + return self._state.statistics() + + def __enter__(self) -> MemoryObjectReceiveStream[T_co]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def __del__(self) -> None: + if not self._closed: + warnings.warn( + f"Unclosed <{self.__class__.__name__} at {id(self):x}>", + ResourceWarning, + source=self, + ) + + +@dataclass(eq=False) +class MemoryObjectSendStream(Generic[T_contra], ObjectSendStream[T_contra]): + _state: MemoryObjectStreamState[T_contra] + _closed: bool = field(init=False, default=False) + + def __post_init__(self) -> None: + self._state.open_send_channels += 1 + + def send_nowait(self, item: T_contra) -> None: + """ + Send an item immediately if it can be done without waiting. + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.BrokenResourceError: if the stream has been closed from the + receiving end + :raises ~anyio.WouldBlock: if the buffer is full and there are no tasks waiting + to receive + + """ + if self._closed: + raise ClosedResourceError + if not self._state.open_receive_channels: + raise BrokenResourceError + + while self._state.waiting_receivers: + receive_event, receiver = self._state.waiting_receivers.popitem(last=False) + if not receiver.task_info.has_pending_cancellation(): + receiver.item = item + receive_event.set() + return + + if len(self._state.buffer) < self._state.max_buffer_size: + self._state.buffer.append(item) + else: + raise WouldBlock + + async def send(self, item: T_contra) -> None: + """ + Send an item to the stream. + + If the buffer is full, this method blocks until there is again room in the + buffer or the item can be sent directly to a receiver. + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.BrokenResourceError: if the stream has been closed from the + receiving end + + """ + await checkpoint() + try: + self.send_nowait(item) + except WouldBlock: + # Wait until there's someone on the receiving end + send_event = Event() + self._state.waiting_senders[send_event] = item + try: + await send_event.wait() + except BaseException: + self._state.waiting_senders.pop(send_event, None) + raise + + if send_event in self._state.waiting_senders: + del self._state.waiting_senders[send_event] + raise BrokenResourceError from None + + def clone(self) -> MemoryObjectSendStream[T_contra]: + """ + Create a clone of this send stream. + + Each clone can be closed separately. Only when all clones have been closed will + the sending end of the memory stream be considered closed by the receiving ends. + + :return: the cloned stream + + """ + if self._closed: + raise ClosedResourceError + + return MemoryObjectSendStream(_state=self._state) + + def close(self) -> None: + """ + Close the stream. + + This works the exact same way as :meth:`aclose`, but is provided as a special + case for the benefit of synchronous callbacks. + + """ + if not self._closed: + self._closed = True + self._state.open_send_channels -= 1 + if self._state.open_send_channels == 0: + receive_events = list(self._state.waiting_receivers.keys()) + self._state.waiting_receivers.clear() + for event in receive_events: + event.set() + + async def aclose(self) -> None: + self.close() + + def statistics(self) -> MemoryObjectStreamStatistics: + """ + Return statistics about the current state of this stream. + + .. versionadded:: 3.0 + """ + return self._state.statistics() + + def __enter__(self) -> MemoryObjectSendStream[T_contra]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def __del__(self) -> None: + if not self._closed: + warnings.warn( + f"Unclosed <{self.__class__.__name__} at {id(self):x}>", + ResourceWarning, + source=self, + ) diff --git a/.venv/Lib/site-packages/anyio/streams/stapled.py b/.venv/Lib/site-packages/anyio/streams/stapled.py new file mode 100644 index 0000000..80f64a2 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/streams/stapled.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from typing import Any, Generic, TypeVar + +from ..abc import ( + ByteReceiveStream, + ByteSendStream, + ByteStream, + Listener, + ObjectReceiveStream, + ObjectSendStream, + ObjectStream, + TaskGroup, +) + +T_Item = TypeVar("T_Item") +T_Stream = TypeVar("T_Stream") + + +@dataclass(eq=False) +class StapledByteStream(ByteStream): + """ + Combines two byte streams into a single, bidirectional byte stream. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param ByteSendStream send_stream: the sending byte stream + :param ByteReceiveStream receive_stream: the receiving byte stream + """ + + send_stream: ByteSendStream + receive_stream: ByteReceiveStream + + async def receive(self, max_bytes: int = 65536) -> bytes: + return await self.receive_stream.receive(max_bytes) + + async def send(self, item: bytes) -> None: + await self.send_stream.send(item) + + async def send_eof(self) -> None: + await self.send_stream.aclose() + + async def aclose(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.send_stream.extra_attributes, + **self.receive_stream.extra_attributes, + } + + +@dataclass(eq=False) +class StapledObjectStream(Generic[T_Item], ObjectStream[T_Item]): + """ + Combines two object streams into a single, bidirectional object stream. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param ObjectSendStream send_stream: the sending object stream + :param ObjectReceiveStream receive_stream: the receiving object stream + """ + + send_stream: ObjectSendStream[T_Item] + receive_stream: ObjectReceiveStream[T_Item] + + async def receive(self) -> T_Item: + return await self.receive_stream.receive() + + async def send(self, item: T_Item) -> None: + await self.send_stream.send(item) + + async def send_eof(self) -> None: + await self.send_stream.aclose() + + async def aclose(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.send_stream.extra_attributes, + **self.receive_stream.extra_attributes, + } + + +@dataclass(eq=False) +class MultiListener(Generic[T_Stream], Listener[T_Stream]): + """ + Combines multiple listeners into one, serving connections from all of them at once. + + Any MultiListeners in the given collection of listeners will have their listeners + moved into this one. + + Extra attributes are provided from each listener, with each successive listener + overriding any conflicting attributes from the previous one. + + :param listeners: listeners to serve + :type listeners: Sequence[Listener[T_Stream]] + """ + + listeners: Sequence[Listener[T_Stream]] + + def __post_init__(self) -> None: + listeners: list[Listener[T_Stream]] = [] + for listener in self.listeners: + if isinstance(listener, MultiListener): + listeners.extend(listener.listeners) + del listener.listeners[:] # type: ignore[attr-defined] + else: + listeners.append(listener) + + self.listeners = listeners + + async def serve( + self, handler: Callable[[T_Stream], Any], task_group: TaskGroup | None = None + ) -> None: + from .. import create_task_group + + async with create_task_group() as tg: + for listener in self.listeners: + tg.start_soon(listener.serve, handler, task_group) + + async def aclose(self) -> None: + for listener in self.listeners: + await listener.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + attributes: dict = {} + for listener in self.listeners: + attributes.update(listener.extra_attributes) + + return attributes diff --git a/.venv/Lib/site-packages/anyio/streams/text.py b/.venv/Lib/site-packages/anyio/streams/text.py new file mode 100644 index 0000000..f1a1127 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/streams/text.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +import codecs +from collections.abc import Callable, Mapping +from dataclasses import InitVar, dataclass, field +from typing import Any + +from ..abc import ( + AnyByteReceiveStream, + AnyByteSendStream, + AnyByteStream, + ObjectReceiveStream, + ObjectSendStream, + ObjectStream, +) + + +@dataclass(eq=False) +class TextReceiveStream(ObjectReceiveStream[str]): + """ + Stream wrapper that decodes bytes to strings using the given encoding. + + Decoding is done using :class:`~codecs.IncrementalDecoder` which returns any + completely received unicode characters as soon as they come in. + + :param transport_stream: any bytes-based receive stream + :param encoding: character encoding to use for decoding bytes to strings (defaults + to ``utf-8``) + :param errors: handling scheme for decoding errors (defaults to ``strict``; see the + `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteReceiveStream + encoding: InitVar[str] = "utf-8" + errors: InitVar[str] = "strict" + _decoder: codecs.IncrementalDecoder = field(init=False) + + def __post_init__(self, encoding: str, errors: str) -> None: + decoder_class = codecs.getincrementaldecoder(encoding) + self._decoder = decoder_class(errors=errors) + + async def receive(self) -> str: + while True: + chunk = await self.transport_stream.receive() + decoded = self._decoder.decode(chunk) + if decoded: + return decoded + + async def aclose(self) -> None: + await self.transport_stream.aclose() + self._decoder.reset() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.transport_stream.extra_attributes + + +@dataclass(eq=False) +class TextSendStream(ObjectSendStream[str]): + """ + Sends strings to the wrapped stream as bytes using the given encoding. + + :param AnyByteSendStream transport_stream: any bytes-based send stream + :param str encoding: character encoding to use for encoding strings to bytes + (defaults to ``utf-8``) + :param str errors: handling scheme for encoding errors (defaults to ``strict``; see + the `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteSendStream + encoding: InitVar[str] = "utf-8" + errors: str = "strict" + _encoder: Callable[..., tuple[bytes, int]] = field(init=False) + + def __post_init__(self, encoding: str) -> None: + self._encoder = codecs.getencoder(encoding) + + async def send(self, item: str) -> None: + encoded = self._encoder(item, self.errors)[0] + await self.transport_stream.send(encoded) + + async def aclose(self) -> None: + await self.transport_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.transport_stream.extra_attributes + + +@dataclass(eq=False) +class TextStream(ObjectStream[str]): + """ + A bidirectional stream that decodes bytes to strings on receive and encodes strings + to bytes on send. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param AnyByteStream transport_stream: any bytes-based stream + :param str encoding: character encoding to use for encoding/decoding strings to/from + bytes (defaults to ``utf-8``) + :param str errors: handling scheme for encoding errors (defaults to ``strict``; see + the `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteStream + encoding: InitVar[str] = "utf-8" + errors: InitVar[str] = "strict" + _receive_stream: TextReceiveStream = field(init=False) + _send_stream: TextSendStream = field(init=False) + + def __post_init__(self, encoding: str, errors: str) -> None: + self._receive_stream = TextReceiveStream( + self.transport_stream, encoding=encoding, errors=errors + ) + self._send_stream = TextSendStream( + self.transport_stream, encoding=encoding, errors=errors + ) + + async def receive(self) -> str: + return await self._receive_stream.receive() + + async def send(self, item: str) -> None: + await self._send_stream.send(item) + + async def send_eof(self) -> None: + await self.transport_stream.send_eof() + + async def aclose(self) -> None: + await self._send_stream.aclose() + await self._receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self._send_stream.extra_attributes, + **self._receive_stream.extra_attributes, + } diff --git a/.venv/Lib/site-packages/anyio/streams/tls.py b/.venv/Lib/site-packages/anyio/streams/tls.py new file mode 100644 index 0000000..70a41cc --- /dev/null +++ b/.venv/Lib/site-packages/anyio/streams/tls.py @@ -0,0 +1,352 @@ +from __future__ import annotations + +import logging +import re +import ssl +import sys +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from functools import wraps +from typing import Any, TypeVar + +from .. import ( + BrokenResourceError, + EndOfStream, + aclose_forcefully, + get_cancelled_exc_class, + to_thread, +) +from .._core._typedattr import TypedAttributeSet, typed_attribute +from ..abc import AnyByteStream, ByteStream, Listener, TaskGroup + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") +_PCTRTT = tuple[tuple[str, str], ...] +_PCTRTTT = tuple[_PCTRTT, ...] + + +class TLSAttribute(TypedAttributeSet): + """Contains Transport Layer Security related attributes.""" + + #: the selected ALPN protocol + alpn_protocol: str | None = typed_attribute() + #: the channel binding for type ``tls-unique`` + channel_binding_tls_unique: bytes = typed_attribute() + #: the selected cipher + cipher: tuple[str, str, int] = typed_attribute() + #: the peer certificate in dictionary form (see :meth:`ssl.SSLSocket.getpeercert` + # for more information) + peer_certificate: None | (dict[str, str | _PCTRTTT | _PCTRTT]) = typed_attribute() + #: the peer certificate in binary form + peer_certificate_binary: bytes | None = typed_attribute() + #: ``True`` if this is the server side of the connection + server_side: bool = typed_attribute() + #: ciphers shared by the client during the TLS handshake (``None`` if this is the + #: client side) + shared_ciphers: list[tuple[str, str, int]] | None = typed_attribute() + #: the :class:`~ssl.SSLObject` used for encryption + ssl_object: ssl.SSLObject = typed_attribute() + #: ``True`` if this stream does (and expects) a closing TLS handshake when the + #: stream is being closed + standard_compatible: bool = typed_attribute() + #: the TLS protocol version (e.g. ``TLSv1.2``) + tls_version: str = typed_attribute() + + +@dataclass(eq=False) +class TLSStream(ByteStream): + """ + A stream wrapper that encrypts all sent data and decrypts received data. + + This class has no public initializer; use :meth:`wrap` instead. + All extra attributes from :class:`~TLSAttribute` are supported. + + :var AnyByteStream transport_stream: the wrapped stream + + """ + + transport_stream: AnyByteStream + standard_compatible: bool + _ssl_object: ssl.SSLObject + _read_bio: ssl.MemoryBIO + _write_bio: ssl.MemoryBIO + + @classmethod + async def wrap( + cls, + transport_stream: AnyByteStream, + *, + server_side: bool | None = None, + hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + standard_compatible: bool = True, + ) -> TLSStream: + """ + Wrap an existing stream with Transport Layer Security. + + This performs a TLS handshake with the peer. + + :param transport_stream: a bytes-transporting stream to wrap + :param server_side: ``True`` if this is the server side of the connection, + ``False`` if this is the client side (if omitted, will be set to ``False`` + if ``hostname`` has been provided, ``False`` otherwise). Used only to create + a default context when an explicit context has not been provided. + :param hostname: host name of the peer (if host name checking is desired) + :param ssl_context: the SSLContext object to use (if not provided, a secure + default will be created) + :param standard_compatible: if ``False``, skip the closing handshake when + closing the connection, and don't raise an exception if the peer does the + same + :raises ~ssl.SSLError: if the TLS handshake fails + + """ + if server_side is None: + server_side = not hostname + + if not ssl_context: + purpose = ( + ssl.Purpose.CLIENT_AUTH if server_side else ssl.Purpose.SERVER_AUTH + ) + ssl_context = ssl.create_default_context(purpose) + + # Re-enable detection of unexpected EOFs if it was disabled by Python + if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"): + ssl_context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF + + bio_in = ssl.MemoryBIO() + bio_out = ssl.MemoryBIO() + + # External SSLContext implementations may do blocking I/O in wrap_bio(), + # but the standard library implementation won't + if type(ssl_context) is ssl.SSLContext: + ssl_object = ssl_context.wrap_bio( + bio_in, bio_out, server_side=server_side, server_hostname=hostname + ) + else: + ssl_object = await to_thread.run_sync( + ssl_context.wrap_bio, + bio_in, + bio_out, + server_side, + hostname, + None, + ) + + wrapper = cls( + transport_stream=transport_stream, + standard_compatible=standard_compatible, + _ssl_object=ssl_object, + _read_bio=bio_in, + _write_bio=bio_out, + ) + await wrapper._call_sslobject_method(ssl_object.do_handshake) + return wrapper + + async def _call_sslobject_method( + self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] + ) -> T_Retval: + while True: + try: + result = func(*args) + except ssl.SSLWantReadError: + try: + # Flush any pending writes first + if self._write_bio.pending: + await self.transport_stream.send(self._write_bio.read()) + + data = await self.transport_stream.receive() + except EndOfStream: + self._read_bio.write_eof() + except OSError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + raise BrokenResourceError from exc + else: + self._read_bio.write(data) + except ssl.SSLWantWriteError: + await self.transport_stream.send(self._write_bio.read()) + except ssl.SSLSyscallError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + raise BrokenResourceError from exc + except ssl.SSLError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + if isinstance(exc, ssl.SSLEOFError) or ( + exc.strerror and "UNEXPECTED_EOF_WHILE_READING" in exc.strerror + ): + if self.standard_compatible: + raise BrokenResourceError from exc + else: + raise EndOfStream from None + + raise + else: + # Flush any pending writes first + if self._write_bio.pending: + await self.transport_stream.send(self._write_bio.read()) + + return result + + async def unwrap(self) -> tuple[AnyByteStream, bytes]: + """ + Does the TLS closing handshake. + + :return: a tuple of (wrapped byte stream, bytes left in the read buffer) + + """ + await self._call_sslobject_method(self._ssl_object.unwrap) + self._read_bio.write_eof() + self._write_bio.write_eof() + return self.transport_stream, self._read_bio.read() + + async def aclose(self) -> None: + if self.standard_compatible: + try: + await self.unwrap() + except BaseException: + await aclose_forcefully(self.transport_stream) + raise + + await self.transport_stream.aclose() + + async def receive(self, max_bytes: int = 65536) -> bytes: + data = await self._call_sslobject_method(self._ssl_object.read, max_bytes) + if not data: + raise EndOfStream + + return data + + async def send(self, item: bytes) -> None: + await self._call_sslobject_method(self._ssl_object.write, item) + + async def send_eof(self) -> None: + tls_version = self.extra(TLSAttribute.tls_version) + match = re.match(r"TLSv(\d+)(?:\.(\d+))?", tls_version) + if match: + major, minor = int(match.group(1)), int(match.group(2) or 0) + if (major, minor) < (1, 3): + raise NotImplementedError( + f"send_eof() requires at least TLSv1.3; current " + f"session uses {tls_version}" + ) + + raise NotImplementedError( + "send_eof() has not yet been implemented for TLS streams" + ) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.transport_stream.extra_attributes, + TLSAttribute.alpn_protocol: self._ssl_object.selected_alpn_protocol, + TLSAttribute.channel_binding_tls_unique: ( + self._ssl_object.get_channel_binding + ), + TLSAttribute.cipher: self._ssl_object.cipher, + TLSAttribute.peer_certificate: lambda: self._ssl_object.getpeercert(False), + TLSAttribute.peer_certificate_binary: lambda: self._ssl_object.getpeercert( + True + ), + TLSAttribute.server_side: lambda: self._ssl_object.server_side, + TLSAttribute.shared_ciphers: lambda: self._ssl_object.shared_ciphers() + if self._ssl_object.server_side + else None, + TLSAttribute.standard_compatible: lambda: self.standard_compatible, + TLSAttribute.ssl_object: lambda: self._ssl_object, + TLSAttribute.tls_version: self._ssl_object.version, + } + + +@dataclass(eq=False) +class TLSListener(Listener[TLSStream]): + """ + A convenience listener that wraps another listener and auto-negotiates a TLS session + on every accepted connection. + + If the TLS handshake times out or raises an exception, + :meth:`handle_handshake_error` is called to do whatever post-mortem processing is + deemed necessary. + + Supports only the :attr:`~TLSAttribute.standard_compatible` extra attribute. + + :param Listener listener: the listener to wrap + :param ssl_context: the SSL context object + :param standard_compatible: a flag passed through to :meth:`TLSStream.wrap` + :param handshake_timeout: time limit for the TLS handshake + (passed to :func:`~anyio.fail_after`) + """ + + listener: Listener[Any] + ssl_context: ssl.SSLContext + standard_compatible: bool = True + handshake_timeout: float = 30 + + @staticmethod + async def handle_handshake_error(exc: BaseException, stream: AnyByteStream) -> None: + """ + Handle an exception raised during the TLS handshake. + + This method does 3 things: + + #. Forcefully closes the original stream + #. Logs the exception (unless it was a cancellation exception) using the + ``anyio.streams.tls`` logger + #. Reraises the exception if it was a base exception or a cancellation exception + + :param exc: the exception + :param stream: the original stream + + """ + await aclose_forcefully(stream) + + # Log all except cancellation exceptions + if not isinstance(exc, get_cancelled_exc_class()): + # CPython (as of 3.11.5) returns incorrect `sys.exc_info()` here when using + # any asyncio implementation, so we explicitly pass the exception to log + # (https://github.com/python/cpython/issues/108668). Trio does not have this + # issue because it works around the CPython bug. + logging.getLogger(__name__).exception( + "Error during TLS handshake", exc_info=exc + ) + + # Only reraise base exceptions and cancellation exceptions + if not isinstance(exc, Exception) or isinstance(exc, get_cancelled_exc_class()): + raise + + async def serve( + self, + handler: Callable[[TLSStream], Any], + task_group: TaskGroup | None = None, + ) -> None: + @wraps(handler) + async def handler_wrapper(stream: AnyByteStream) -> None: + from .. import fail_after + + try: + with fail_after(self.handshake_timeout): + wrapped_stream = await TLSStream.wrap( + stream, + ssl_context=self.ssl_context, + standard_compatible=self.standard_compatible, + ) + except BaseException as exc: + await self.handle_handshake_error(exc, stream) + else: + await handler(wrapped_stream) + + await self.listener.serve(handler_wrapper, task_group) + + async def aclose(self) -> None: + await self.listener.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + TLSAttribute.standard_compatible: lambda: self.standard_compatible, + } diff --git a/.venv/Lib/site-packages/anyio/to_interpreter.py b/.venv/Lib/site-packages/anyio/to_interpreter.py new file mode 100644 index 0000000..8a2e993 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/to_interpreter.py @@ -0,0 +1,218 @@ +from __future__ import annotations + +import atexit +import os +import pickle +import sys +from collections import deque +from collections.abc import Callable +from textwrap import dedent +from typing import Any, Final, TypeVar + +from . import current_time, to_thread +from ._core._exceptions import BrokenWorkerIntepreter +from ._core._synchronization import CapacityLimiter +from .lowlevel import RunVar + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +UNBOUND: Final = 2 # I have no clue how this works, but it was used in the stdlib +FMT_UNPICKLED: Final = 0 +FMT_PICKLED: Final = 1 +DEFAULT_CPU_COUNT: Final = 8 # this is just an arbitrarily selected value +MAX_WORKER_IDLE_TIME = ( + 30 # seconds a subinterpreter can be idle before becoming eligible for pruning +) + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +_idle_workers = RunVar[deque["Worker"]]("_available_workers") +_default_interpreter_limiter = RunVar[CapacityLimiter]("_default_interpreter_limiter") + + +class Worker: + _run_func = compile( + dedent(""" + import _interpqueues as queues + import _interpreters as interpreters + from pickle import loads, dumps, HIGHEST_PROTOCOL + + item = queues.get(queue_id)[0] + try: + func, args = loads(item) + retval = func(*args) + except BaseException as exc: + is_exception = True + retval = exc + else: + is_exception = False + + try: + queues.put(queue_id, (retval, is_exception), FMT_UNPICKLED, UNBOUND) + except interpreters.NotShareableError: + retval = dumps(retval, HIGHEST_PROTOCOL) + queues.put(queue_id, (retval, is_exception), FMT_PICKLED, UNBOUND) + """), + "", + "exec", + ) + + last_used: float = 0 + + _initialized: bool = False + _interpreter_id: int + _queue_id: int + + def initialize(self) -> None: + import _interpqueues as queues + import _interpreters as interpreters + + self._interpreter_id = interpreters.create() + self._queue_id = queues.create(2, FMT_UNPICKLED, UNBOUND) + self._initialized = True + interpreters.set___main___attrs( + self._interpreter_id, + { + "queue_id": self._queue_id, + "FMT_PICKLED": FMT_PICKLED, + "FMT_UNPICKLED": FMT_UNPICKLED, + "UNBOUND": UNBOUND, + }, + ) + + def destroy(self) -> None: + import _interpqueues as queues + import _interpreters as interpreters + + if self._initialized: + interpreters.destroy(self._interpreter_id) + queues.destroy(self._queue_id) + + def _call( + self, + func: Callable[..., T_Retval], + args: tuple[Any], + ) -> tuple[Any, bool]: + import _interpqueues as queues + import _interpreters as interpreters + + if not self._initialized: + self.initialize() + + payload = pickle.dumps((func, args), pickle.HIGHEST_PROTOCOL) + queues.put(self._queue_id, payload, FMT_PICKLED, UNBOUND) + + res: Any + is_exception: bool + if exc_info := interpreters.exec(self._interpreter_id, self._run_func): + raise BrokenWorkerIntepreter(exc_info) + + (res, is_exception), fmt = queues.get(self._queue_id)[:2] + if fmt == FMT_PICKLED: + res = pickle.loads(res) + + return res, is_exception + + async def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any], + limiter: CapacityLimiter, + ) -> T_Retval: + result, is_exception = await to_thread.run_sync( + self._call, + func, + args, + limiter=limiter, + ) + if is_exception: + raise result + + return result + + +def _stop_workers(workers: deque[Worker]) -> None: + for worker in workers: + worker.destroy() + + workers.clear() + + +async def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a subinterpreter. + + If the ``cancellable`` option is enabled and the task waiting for its completion is + cancelled, the call will still run its course but its return value (or any raised + exception) will be ignored. + + .. warning:: This feature is **experimental**. The upstream interpreter API has not + yet been finalized or thoroughly tested, so don't rely on this for anything + mission critical. + + :param func: a callable + :param args: positional arguments for the callable + :param limiter: capacity limiter to use to limit the total amount of subinterpreters + running (if omitted, the default limiter is used) + :return: the result of the call + :raises BrokenWorkerIntepreter: if there's an internal error in a subinterpreter + + """ + if sys.version_info <= (3, 13): + raise RuntimeError("subinterpreters require at least Python 3.13") + + if limiter is None: + limiter = current_default_interpreter_limiter() + + try: + idle_workers = _idle_workers.get() + except LookupError: + idle_workers = deque() + _idle_workers.set(idle_workers) + atexit.register(_stop_workers, idle_workers) + + async with limiter: + try: + worker = idle_workers.pop() + except IndexError: + worker = Worker() + + try: + return await worker.call(func, args, limiter) + finally: + # Prune workers that have been idle for too long + now = current_time() + while idle_workers: + if now - idle_workers[0].last_used <= MAX_WORKER_IDLE_TIME: + break + + await to_thread.run_sync(idle_workers.popleft().destroy, limiter=limiter) + + worker.last_used = current_time() + idle_workers.append(worker) + + +def current_default_interpreter_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of + concurrently running subinterpreters. + + Defaults to the number of CPU cores. + + :return: a capacity limiter object + + """ + try: + return _default_interpreter_limiter.get() + except LookupError: + limiter = CapacityLimiter(os.cpu_count() or DEFAULT_CPU_COUNT) + _default_interpreter_limiter.set(limiter) + return limiter diff --git a/.venv/Lib/site-packages/anyio/to_process.py b/.venv/Lib/site-packages/anyio/to_process.py new file mode 100644 index 0000000..495de2a --- /dev/null +++ b/.venv/Lib/site-packages/anyio/to_process.py @@ -0,0 +1,258 @@ +from __future__ import annotations + +import os +import pickle +import subprocess +import sys +from collections import deque +from collections.abc import Callable +from importlib.util import module_from_spec, spec_from_file_location +from typing import TypeVar, cast + +from ._core._eventloop import current_time, get_async_backend, get_cancelled_exc_class +from ._core._exceptions import BrokenWorkerProcess +from ._core._subprocesses import open_process +from ._core._synchronization import CapacityLimiter +from ._core._tasks import CancelScope, fail_after +from .abc import ByteReceiveStream, ByteSendStream, Process +from .lowlevel import RunVar, checkpoint_if_cancelled +from .streams.buffered import BufferedByteReceiveStream + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +WORKER_MAX_IDLE_TIME = 300 # 5 minutes + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +_process_pool_workers: RunVar[set[Process]] = RunVar("_process_pool_workers") +_process_pool_idle_workers: RunVar[deque[tuple[Process, float]]] = RunVar( + "_process_pool_idle_workers" +) +_default_process_limiter: RunVar[CapacityLimiter] = RunVar("_default_process_limiter") + + +async def run_sync( # type: ignore[return] + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + cancellable: bool = False, + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a worker process. + + If the ``cancellable`` option is enabled and the task waiting for its completion is + cancelled, the worker process running it will be abruptly terminated using SIGKILL + (or ``terminateProcess()`` on Windows). + + :param func: a callable + :param args: positional arguments for the callable + :param cancellable: ``True`` to allow cancellation of the operation while it's + running + :param limiter: capacity limiter to use to limit the total amount of processes + running (if omitted, the default limiter is used) + :return: an awaitable that yields the return value of the function. + + """ + + async def send_raw_command(pickled_cmd: bytes) -> object: + try: + await stdin.send(pickled_cmd) + response = await buffered.receive_until(b"\n", 50) + status, length = response.split(b" ") + if status not in (b"RETURN", b"EXCEPTION"): + raise RuntimeError( + f"Worker process returned unexpected response: {response!r}" + ) + + pickled_response = await buffered.receive_exactly(int(length)) + except BaseException as exc: + workers.discard(process) + try: + process.kill() + with CancelScope(shield=True): + await process.aclose() + except ProcessLookupError: + pass + + if isinstance(exc, get_cancelled_exc_class()): + raise + else: + raise BrokenWorkerProcess from exc + + retval = pickle.loads(pickled_response) + if status == b"EXCEPTION": + assert isinstance(retval, BaseException) + raise retval + else: + return retval + + # First pickle the request before trying to reserve a worker process + await checkpoint_if_cancelled() + request = pickle.dumps(("run", func, args), protocol=pickle.HIGHEST_PROTOCOL) + + # If this is the first run in this event loop thread, set up the necessary variables + try: + workers = _process_pool_workers.get() + idle_workers = _process_pool_idle_workers.get() + except LookupError: + workers = set() + idle_workers = deque() + _process_pool_workers.set(workers) + _process_pool_idle_workers.set(idle_workers) + get_async_backend().setup_process_pool_exit_at_shutdown(workers) + + async with limiter or current_default_process_limiter(): + # Pop processes from the pool (starting from the most recently used) until we + # find one that hasn't exited yet + process: Process + while idle_workers: + process, idle_since = idle_workers.pop() + if process.returncode is None: + stdin = cast(ByteSendStream, process.stdin) + buffered = BufferedByteReceiveStream( + cast(ByteReceiveStream, process.stdout) + ) + + # Prune any other workers that have been idle for WORKER_MAX_IDLE_TIME + # seconds or longer + now = current_time() + killed_processes: list[Process] = [] + while idle_workers: + if now - idle_workers[0][1] < WORKER_MAX_IDLE_TIME: + break + + process_to_kill, idle_since = idle_workers.popleft() + process_to_kill.kill() + workers.remove(process_to_kill) + killed_processes.append(process_to_kill) + + with CancelScope(shield=True): + for killed_process in killed_processes: + await killed_process.aclose() + + break + + workers.remove(process) + else: + command = [sys.executable, "-u", "-m", __name__] + process = await open_process( + command, stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) + try: + stdin = cast(ByteSendStream, process.stdin) + buffered = BufferedByteReceiveStream( + cast(ByteReceiveStream, process.stdout) + ) + with fail_after(20): + message = await buffered.receive(6) + + if message != b"READY\n": + raise BrokenWorkerProcess( + f"Worker process returned unexpected response: {message!r}" + ) + + main_module_path = getattr(sys.modules["__main__"], "__file__", None) + pickled = pickle.dumps( + ("init", sys.path, main_module_path), + protocol=pickle.HIGHEST_PROTOCOL, + ) + await send_raw_command(pickled) + except (BrokenWorkerProcess, get_cancelled_exc_class()): + raise + except BaseException as exc: + process.kill() + raise BrokenWorkerProcess( + "Error during worker process initialization" + ) from exc + + workers.add(process) + + with CancelScope(shield=not cancellable): + try: + return cast(T_Retval, await send_raw_command(request)) + finally: + if process in workers: + idle_workers.append((process, current_time())) + + +def current_default_process_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of worker + processes. + + :return: a capacity limiter object + + """ + try: + return _default_process_limiter.get() + except LookupError: + limiter = CapacityLimiter(os.cpu_count() or 2) + _default_process_limiter.set(limiter) + return limiter + + +def process_worker() -> None: + # Redirect standard streams to os.devnull so that user code won't interfere with the + # parent-worker communication + stdin = sys.stdin + stdout = sys.stdout + sys.stdin = open(os.devnull) + sys.stdout = open(os.devnull, "w") + + stdout.buffer.write(b"READY\n") + while True: + retval = exception = None + try: + command, *args = pickle.load(stdin.buffer) + except EOFError: + return + except BaseException as exc: + exception = exc + else: + if command == "run": + func, args = args + try: + retval = func(*args) + except BaseException as exc: + exception = exc + elif command == "init": + main_module_path: str | None + sys.path, main_module_path = args + del sys.modules["__main__"] + if main_module_path and os.path.isfile(main_module_path): + # Load the parent's main module but as __mp_main__ instead of + # __main__ (like multiprocessing does) to avoid infinite recursion + try: + spec = spec_from_file_location("__mp_main__", main_module_path) + if spec and spec.loader: + main = module_from_spec(spec) + spec.loader.exec_module(main) + sys.modules["__main__"] = main + except BaseException as exc: + exception = exc + try: + if exception is not None: + status = b"EXCEPTION" + pickled = pickle.dumps(exception, pickle.HIGHEST_PROTOCOL) + else: + status = b"RETURN" + pickled = pickle.dumps(retval, pickle.HIGHEST_PROTOCOL) + except BaseException as exc: + exception = exc + status = b"EXCEPTION" + pickled = pickle.dumps(exc, pickle.HIGHEST_PROTOCOL) + + stdout.buffer.write(b"%s %d\n" % (status, len(pickled))) + stdout.buffer.write(pickled) + + # Respect SIGTERM + if isinstance(exception, SystemExit): + raise exception + + +if __name__ == "__main__": + process_worker() diff --git a/.venv/Lib/site-packages/anyio/to_thread.py b/.venv/Lib/site-packages/anyio/to_thread.py new file mode 100644 index 0000000..5070516 --- /dev/null +++ b/.venv/Lib/site-packages/anyio/to_thread.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +import sys +from collections.abc import Callable +from typing import TypeVar +from warnings import warn + +from ._core._eventloop import get_async_backend +from .abc import CapacityLimiter + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + + +async def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + abandon_on_cancel: bool = False, + cancellable: bool | None = None, + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a worker thread. + + If the ``cancellable`` option is enabled and the task waiting for its completion is + cancelled, the thread will still run its course but its return value (or any raised + exception) will be ignored. + + :param func: a callable + :param args: positional arguments for the callable + :param abandon_on_cancel: ``True`` to abandon the thread (leaving it to run + unchecked on own) if the host task is cancelled, ``False`` to ignore + cancellations in the host task until the operation has completed in the worker + thread + :param cancellable: deprecated alias of ``abandon_on_cancel``; will override + ``abandon_on_cancel`` if both parameters are passed + :param limiter: capacity limiter to use to limit the total amount of threads running + (if omitted, the default limiter is used) + :return: an awaitable that yields the return value of the function. + + """ + if cancellable is not None: + abandon_on_cancel = cancellable + warn( + "The `cancellable=` keyword argument to `anyio.to_thread.run_sync` is " + "deprecated since AnyIO 4.1.0; use `abandon_on_cancel=` instead", + DeprecationWarning, + stacklevel=2, + ) + + return await get_async_backend().run_sync_in_worker_thread( + func, args, abandon_on_cancel=abandon_on_cancel, limiter=limiter + ) + + +def current_default_thread_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of + concurrent threads. + + :return: a capacity limiter object + + """ + return get_async_backend().current_default_thread_limiter() diff --git a/.venv/Lib/site-packages/blinker-1.9.0.dist-info/INSTALLER b/.venv/Lib/site-packages/blinker-1.9.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/blinker-1.9.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/blinker-1.9.0.dist-info/LICENSE.txt b/.venv/Lib/site-packages/blinker-1.9.0.dist-info/LICENSE.txt new file mode 100644 index 0000000..79c9825 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/blinker-1.9.0.dist-info/METADATA b/.venv/Lib/site-packages/blinker-1.9.0.dist-info/METADATA new file mode 100644 index 0000000..6d343f5 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/blinker-1.9.0.dist-info/RECORD b/.venv/Lib/site-packages/blinker-1.9.0.dist-info/RECORD new file mode 100644 index 0000000..d4f985b --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/blinker-1.9.0.dist-info/WHEEL b/.venv/Lib/site-packages/blinker-1.9.0.dist-info/WHEEL new file mode 100644 index 0000000..e3c6fee --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/blinker/__init__.py b/.venv/Lib/site-packages/blinker/__init__.py new file mode 100644 index 0000000..1772fa4 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/blinker/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/blinker/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..833376715287da19cc49e384861df9596c690e92 GIT binary patch literal 516 zcmaKoK}*9h6vtoIuI)BNJP3jaQ%^fM@1lY@hJp-Xc(4Rwwq&z5ZAX(0Jo+iTdl5g2 z-=NTwcyVOL|Lle@J%-691SjUTM=@?=gP2@CsNP&SlavnI${`2Q?Af$SoinL`wM&eC&A0> zcxCQc@vQ__8dmHi2AyWnWNb1w^IS5PI~Xh65!dqny259SE5+Cod}0ZF#m9T}0;@C~ z@`U!Jj!ibB!$`>Xd7jp1lMJXojG8UzgVXQm5ErR_d6LTdG^A+rEXQnq%-W@_n30tqQTEcb-8zrol Qu!ify^;@+&KVQbIZ!SHA#{d8T literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/blinker/__pycache__/_utilities.cpython-312.pyc b/.venv/Lib/site-packages/blinker/__pycache__/_utilities.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5176b3f2e58b5fbc017ce0fcb1f98ebd4046314 GIT binary patch literal 2743 zcmb7G&2JM&6rb^~z3Y$IA>p$GSUyx@lp0VEl>pHq5Fb(+(FR({akSa&j>9JFU3X?2 zu;o^%N`)gt35O~7U2+_ZzmtKNWsb~vQrP4!hpimX{)Hkzsa41yjNc-l^dv9ic z`@Uv>?CD7%7Xw#cJhrgJe_&pr0ikTQXgPg=w+;g z)AY$@)6A=(VtXu9eQ;u^fYZaIY1-mjO_PcI1_#gmT{(HAbdeFtN~2b#bc(PU9#l)C zu0xFTG!TJerPCJUC(fTUW|?ss_Ro~gTJ{XY^HeY$lnS%NpDhi$lO^VI@>Ug8))Zl- zNze7c=%88SuIF->utK#?d*Ho|Y!tsj8|hY%v3G$ypF)RgW`dX<~I0CH;lyI&LR39)= zm`q_LM;*~LFn2M{!IY1MDkEN5E|}P87*uQvb<=cxmz!oIw}v|s7@%gt_kerj)|PrB zN&8`Cx8nyaBndnc!W+N^u;M6?9XH4^xn&q`MP!>*Ln`TriIn(Uwogfu?Nw)|h;zWg?!k%`y z*u?Xoo-o}2Qt6WSWM)Y0l86YfJdo#KqNgz}u0QERg+p^^mU|AYs0aU46Y>7lUL4=H zngH?_kOAosxp!T*#NiB?Ek6LWi1=D2O|SI{P_)O3PC*ZFu~QDnFu2TK9-W@D&QAFL zrO~k~LvP`tJmhF1E_P)62NdL#*u&uJL9#AHA76-p#@MJ z5XujL%%NvG(sMs+TkdOHmXx2gfoBQS+qbZHap>02N^aM8ecznCGyLuF%I?Fr) z;m6vBg}t|x+oKDOpR_$sz_`Daf<2jR%f9~3+(=Ze-SHL8SrPY337G$p1)1&$G;t?7 z5E%^)lA{TC)eE_7fKYw=K}QD#5h0}F{ZMeiAE7QT=1FMk>wBVf4g7AXthgFg#s3ltoS z!kis=9%PI7b1GPqcBlw~ZRv?H!RuAmp9(P#waf4?CB9SmM0zLGF4U_8eitG{qK0IG zNKf2FQDH|H$FZmv9jC%UqLkSYAWsyG@gub7ceMQ%l>G(uJwjU^p`E{~J##0no%{S; d%l=|y4*!+djt8D1Agjl6xc|?6D&8s2{s$8=X9xfQ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/blinker/__pycache__/base.cpython-312.pyc b/.venv/Lib/site-packages/blinker/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e1f5fdf26dbaa67aec7da9cd19a453af48b4673 GIT binary patch literal 21986 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 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/.venv/Lib/site-packages/blinker/base.py b/.venv/Lib/site-packages/blinker/base.py new file mode 100644 index 0000000..d051b94 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/blinker/py.typed b/.venv/Lib/site-packages/blinker/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/INSTALLER b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/METADATA b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/METADATA new file mode 100644 index 0000000..bba2b69 --- /dev/null +++ b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/METADATA @@ -0,0 +1,78 @@ +Metadata-Version: 2.4 +Name: certifi +Version: 2025.4.26 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: https://github.com/certifi/python-certifi +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Project-URL: Source, https://github.com/certifi/python-certifi +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Requires-Python: >=3.6 +License-File: LICENSE +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: project-url +Dynamic: requires-python +Dynamic: summary + +Certifi: Python SSL Certificates +================================ + +Certifi provides Mozilla's carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python3.7/site-packages/certifi/cacert.pem + +Enjoy! + +.. _`Requests`: https://requests.readthedocs.io/en/master/ + +Addition/Removal of Certificates +-------------------------------- + +Certifi does not support any addition/removal or other modification of the +CA trust store content. This project is intended to provide a reliable and +highly portable root of trust to python deployments. Look to upstream projects +for methods to use alternate trust. diff --git a/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/RECORD b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/RECORD new file mode 100644 index 0000000..715a9b5 --- /dev/null +++ b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/RECORD @@ -0,0 +1,14 @@ +certifi-2025.4.26.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +certifi-2025.4.26.dist-info/METADATA,sha256=Q1SDFkY5LOQAJmDltZz2wU3VTv1Kh5X-rjGI4KiPHNM,2473 +certifi-2025.4.26.dist-info/RECORD,, +certifi-2025.4.26.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91 +certifi-2025.4.26.dist-info/licenses/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989 +certifi-2025.4.26.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi/__init__.py,sha256=9pyWUGr6sbAlksfOHo0BTV0Gxljjh4IK1kXAjHgjL4I,94 +certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 +certifi/__pycache__/__init__.cpython-312.pyc,, +certifi/__pycache__/__main__.cpython-312.pyc,, +certifi/__pycache__/core.cpython-312.pyc,, +certifi/cacert.pem,sha256=K3sQJvGKKX4hSBicoMvn0-f578NvcjHMwoIKQE_rVZY,283771 +certifi/core.py,sha256=qRDDFyXVJwTB_EmoGppaXU_R9qCZvhl-EzxPMuV3nTA,4426 +certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/WHEEL b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/WHEEL new file mode 100644 index 0000000..8acb955 --- /dev/null +++ b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (79.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/licenses/LICENSE b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/licenses/LICENSE new file mode 100644 index 0000000..62b076c --- /dev/null +++ b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/top_level.txt b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/top_level.txt new file mode 100644 index 0000000..963eac5 --- /dev/null +++ b/.venv/Lib/site-packages/certifi-2025.4.26.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/.venv/Lib/site-packages/certifi/__init__.py b/.venv/Lib/site-packages/certifi/__init__.py new file mode 100644 index 0000000..bf83fa9 --- /dev/null +++ b/.venv/Lib/site-packages/certifi/__init__.py @@ -0,0 +1,4 @@ +from .core import contents, where + +__all__ = ["contents", "where"] +__version__ = "2025.04.26" diff --git a/.venv/Lib/site-packages/certifi/__main__.py b/.venv/Lib/site-packages/certifi/__main__.py new file mode 100644 index 0000000..8945b5d --- /dev/null +++ b/.venv/Lib/site-packages/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/.venv/Lib/site-packages/certifi/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/certifi/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce65cc85b9c938f1a0a48e73ca6df6c2359f6065 GIT binary patch literal 331 zcmYjMJxc>Y5Z%2G6LZ8)1RKvXg=aX_B8VV{pdbX*xFwAqa*+2%5NoRCn>c^t_gY_%sz7Y?IA%-)Q;|S+g zWMRZCW)H2%W=`bH+}6@3He)TTswbk*P-s=T(-BCJw(+H7=%x|2!`-006NGzbEw^$K z@jtF=l*U;`Y2{H`f>c}-ls=)kSw-LJQHPu>&9C@2rM%#p(qJ;H>ifB1 n*BKm0&)i--rq&F7*%;$DwE6C>-cMf|8@ILBmGwJME}QHJ_QhL( literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/certifi/__pycache__/__main__.cpython-312.pyc b/.venv/Lib/site-packages/certifi/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57f76b5509105e450448c44c9dea848b78c55e7e GIT binary patch literal 646 zcmZuuO=}b}7*6JU2DcR#VevF5qAPBDFRf5qih`7dBFGXL;!NDm?9K;CcBQ3Nvznj2T}dIww1D<de@(T&2(<2m53dDQX>pLWcsi;T&?zpAaBDh-LyB8y7Ip0D*4|XrO>keQ&u-QHpYC z9x?Ad&g{(2{NC@q*)QYqC;|DW@%GYteT4i6{{+(GJFQCsA@_($^28KOu`J{T&x(1` zOq=pYa$d58k{V3gwWhLMAcP)*r$c3>63T}v;e1#irl_mXBBo?T7eq4*EA;rj3Vv)& z{E_t{o%N!yUOb-BW47i+KQ?YB4I>)F11AJ}-gzmm)+1!5(wzS7CvT?ZcS z?GXIQ55N;qGvWVBM@WR2gQjK;n7vlo?1Qm>Xn2;Wnfyq}YyMeMUHcel?<1GFWkn5( za(lrZTUm?#xg*i&{F2SG700|@wzAaX*J+hy`I42jtBmXAvd(R%nk_p<+i@1MhK?(Z ztyq<;L$fw#S;MJvtIF9}^ckLF+6}cAGJ-RqRiH`GT4zArBOG*FO4z$kObEM49{M8! zStB`NIw!h4#fkBWcV_2{6Z3y?)nd`QY4c+70cqj<7mE{b7v5(UWraDtQn+ZbW$vsL z=4{i-&QPah8Jra^>5PxhyqmqjvX{y_TP{rM#5CRC_|Uy#IMf3CT+MrL8;GL8Z&GA4HkJalL7vE3L(bIX%vNfPZ_j@%pQ6aqocIh+ z^b8pehY9PfxL}tpS3%7T=tsj2x_LV0? zM?d*fWB6=s_-uXnjr!1=x2K-O`tM%7bM=#}U&T&;s}1d_L*ASAL=dBdt@V3Q5J5ja z1d3UJ%z@1B#H!SZE-c`SDSavjvjIzGnnLv?6ft)dof)?MKyX#7YB;7{UCc=yfD}pT zMyREmMSzO$hei$ZmuBI?F=*`Ppn%3&YV%rM8*XSPYud^CN9x)e4edfryYR({9qmG0 zo83`oePUCj(=)^zh%I};8iqf65Rsw7L*(z^?BVopcR7uv@}US1faoIcu$kj*Pf8fi zeR2RxaYLSzVu817ITFj00L{HDrO51@N|Rt*bS8q z1%vEYpt|^$Aww^1pSxeKA3pz+8P=&0M`7)~3^PTKDTp@69HB#C9tad;vVWgh>_asR zt%ZET$Y-_=ZBKpv=2zOpjyll}vfwK3SK&6e)#I?S8OX;ed)acsewD=hW6pI7xsCgA zLy(9LWkx4%&>4c}G2kXR-8%^16IbJ_Ju2qJLxf%n{(sFOk;> z6IMlmTq6Sf*2qoi8d()`;xzpkygw(ova@1Uy%@PNrbkYpIT)d*P~j=)%b;A@)Vaqo1W7CkOAH`^&kH&;M5tfD;8eCcy~b+ma$0Z1XoyKSm}I_u-tn1mce0&EpRKAxO@4| zWv_rv)?>$S3*TyecUSMM-d(@5zBRtJUe`|VsHcBeQU71QQ>;`;lKt2@h>Vo4Y?aGQndD;G8 zEJy^ldx?2hmsd{O^}_=ui*H9j=`mDZa=r$htMIiIi@6~%P{!#=?2G=+`$n6G5jQ;3 z{xWgZ*&yezPZmvF(j(#VfdD@@exB~p$m}<&{%8||2TLet=jG)X3o5pj3wizO!NJAN@DFa*S zN6PSb@^8AQW!lrgG|vjkMSt2wp`{Wf@r~U3Z~4g6L~`?;M(S8Cb!^+%N#$yZQyZ5b z!?iQFHM%o!0&bm+$!0$}lzp5&`h6s<3_tG6G$pW4`wur2u&{+>x*0}KC4H$TKGTen WXuQ>fURqK{aqD23(e6EXQ2h@s%zj(| literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/certifi/core.py b/.venv/Lib/site-packages/certifi/core.py new file mode 100644 index 0000000..91f538b --- /dev/null +++ b/.venv/Lib/site-packages/certifi/core.py @@ -0,0 +1,114 @@ +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem or its contents. +""" +import sys +import atexit + +def exit_cacert_ctx() -> None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] + + +if sys.version_info >= (3, 11): + + from importlib.resources import as_file, files + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +elif sys.version_info >= (3, 7): + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") + +else: + import os + import types + from typing import Union + + Package = Union[types.ModuleType, str] + Resource = Union[str, "os.PathLike"] + + # This fallback will work for Python versions prior to 3.7 that lack the + # importlib.resources module but relies on the existing `where` function + # so won't address issues with environments like PyOxidizer that don't set + # __file__ on modules. + def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict' + ) -> str: + with open(where(), encoding=encoding) as data: + return data.read() + + # If we don't have importlib.resources, then we will just do the old logic + # of assuming we're on the filesystem and munge the path directly. + def where() -> str: + f = os.path.dirname(__file__) + + return os.path.join(f, "cacert.pem") + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/.venv/Lib/site-packages/certifi/py.typed b/.venv/Lib/site-packages/certifi/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/INSTALLER b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/METADATA b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/METADATA new file mode 100644 index 0000000..573d88b --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/METADATA @@ -0,0 +1,731 @@ +Metadata-Version: 2.4 +Name: charset-normalizer +Version: 3.4.2 +Summary: The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet. +Author-email: "Ahmed R. TAHRI" +Maintainer-email: "Ahmed R. TAHRI" +License: MIT +Project-URL: Changelog, https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md +Project-URL: Documentation, https://charset-normalizer.readthedocs.io/ +Project-URL: Code, https://github.com/jawah/charset_normalizer +Project-URL: Issue tracker, https://github.com/jawah/charset_normalizer/issues +Keywords: encoding,charset,charset-detector,detector,normalization,unicode,chardet,detect +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Text Processing :: Linguistic +Classifier: Topic :: Utilities +Classifier: Typing :: Typed +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: unicode-backport +Dynamic: license-file + +

Charset Detection, for Everyone 👋

+ +

+ The Real First Universal Charset Detector
+
+ + + + Download Count Total + + + + +

+

+ Featured Packages
+ + Static Badge + + + Static Badge + +

+

+ In other language (unofficial port - by the community)
+ + Static Badge + +

+ +> A library that helps you read text from an unknown charset encoding.
Motivated by `chardet`, +> I'm trying to resolve the issue by taking a new approach. +> All IANA character set names for which the Python core library provides codecs are supported. + +

+ >>>>> 👉 Try Me Online Now, Then Adopt Me 👈 <<<<< +

+ +This project offers you an alternative to **Universal Charset Encoding Detector**, also known as **Chardet**. + +| Feature | [Chardet](https://github.com/chardet/chardet) | Charset Normalizer | [cChardet](https://github.com/PyYoshi/cChardet) | +|--------------------------------------------------|:---------------------------------------------:|:--------------------------------------------------------------------------------------------------:|:-----------------------------------------------:| +| `Fast` | ❌ | ✅ | ✅ | +| `Universal**` | ❌ | ✅ | ❌ | +| `Reliable` **without** distinguishable standards | ❌ | ✅ | ✅ | +| `Reliable` **with** distinguishable standards | ✅ | ✅ | ✅ | +| `License` | LGPL-2.1
_restrictive_ | MIT | MPL-1.1
_restrictive_ | +| `Native Python` | ✅ | ✅ | ❌ | +| `Detect spoken language` | ❌ | ✅ | N/A | +| `UnicodeDecodeError Safety` | ❌ | ✅ | ❌ | +| `Whl Size (min)` | 193.6 kB | 42 kB | ~200 kB | +| `Supported Encoding` | 33 | 🎉 [99](https://charset-normalizer.readthedocs.io/en/latest/user/support.html#supported-encodings) | 40 | + +

+Reading Normalized TextCat Reading Text +

+ +*\*\* : They are clearly using specific code for a specific encoding even if covering most of used one*
+ +## ⚡ Performance + +This package offer better performance than its counterpart Chardet. Here are some numbers. + +| Package | Accuracy | Mean per file (ms) | File per sec (est) | +|-----------------------------------------------|:--------:|:------------------:|:------------------:| +| [chardet](https://github.com/chardet/chardet) | 86 % | 63 ms | 16 file/sec | +| charset-normalizer | **98 %** | **10 ms** | 100 file/sec | + +| Package | 99th percentile | 95th percentile | 50th percentile | +|-----------------------------------------------|:---------------:|:---------------:|:---------------:| +| [chardet](https://github.com/chardet/chardet) | 265 ms | 71 ms | 7 ms | +| charset-normalizer | 100 ms | 50 ms | 5 ms | + +_updated as of december 2024 using CPython 3.12_ + +Chardet's performance on larger file (1MB+) are very poor. Expect huge difference on large payload. + +> Stats are generated using 400+ files using default parameters. More details on used files, see GHA workflows. +> And yes, these results might change at any time. The dataset can be updated to include more files. +> The actual delays heavily depends on your CPU capabilities. The factors should remain the same. +> Keep in mind that the stats are generous and that Chardet accuracy vs our is measured using Chardet initial capability +> (e.g. Supported Encoding) Challenge-them if you want. + +## ✨ Installation + +Using pip: + +```sh +pip install charset-normalizer -U +``` + +## 🚀 Basic Usage + +### CLI +This package comes with a CLI. + +``` +usage: normalizer [-h] [-v] [-a] [-n] [-m] [-r] [-f] [-t THRESHOLD] + file [file ...] + +The Real First Universal Charset Detector. Discover originating encoding used +on text file. Normalize text to unicode. + +positional arguments: + files File(s) to be analysed + +optional arguments: + -h, --help show this help message and exit + -v, --verbose Display complementary information about file if any. + Stdout will contain logs about the detection process. + -a, --with-alternative + Output complementary possibilities if any. Top-level + JSON WILL be a list. + -n, --normalize Permit to normalize input file. If not set, program + does not write anything. + -m, --minimal Only output the charset detected to STDOUT. Disabling + JSON output. + -r, --replace Replace file when trying to normalize it instead of + creating a new one. + -f, --force Replace file without asking if you are sure, use this + flag with caution. + -t THRESHOLD, --threshold THRESHOLD + Define a custom maximum amount of chaos allowed in + decoded content. 0. <= chaos <= 1. + --version Show version information and exit. +``` + +```bash +normalizer ./data/sample.1.fr.srt +``` + +or + +```bash +python -m charset_normalizer ./data/sample.1.fr.srt +``` + +🎉 Since version 1.4.0 the CLI produce easily usable stdout result in JSON format. + +```json +{ + "path": "/home/default/projects/charset_normalizer/data/sample.1.fr.srt", + "encoding": "cp1252", + "encoding_aliases": [ + "1252", + "windows_1252" + ], + "alternative_encodings": [ + "cp1254", + "cp1256", + "cp1258", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + "mbcs" + ], + "language": "French", + "alphabets": [ + "Basic Latin", + "Latin-1 Supplement" + ], + "has_sig_or_bom": false, + "chaos": 0.149, + "coherence": 97.152, + "unicode_path": null, + "is_preferred": true +} +``` + +### Python +*Just print out normalized text* +```python +from charset_normalizer import from_path + +results = from_path('./my_subtitle.srt') + +print(str(results.best())) +``` + +*Upgrade your code without effort* +```python +from charset_normalizer import detect +``` + +The above code will behave the same as **chardet**. We ensure that we offer the best (reasonable) BC result possible. + +See the docs for advanced usage : [readthedocs.io](https://charset-normalizer.readthedocs.io/en/latest/) + +## 😇 Why + +When I started using Chardet, I noticed that it was not suited to my expectations, and I wanted to propose a +reliable alternative using a completely different method. Also! I never back down on a good challenge! + +I **don't care** about the **originating charset** encoding, because **two different tables** can +produce **two identical rendered string.** +What I want is to get readable text, the best I can. + +In a way, **I'm brute forcing text decoding.** How cool is that ? 😎 + +Don't confuse package **ftfy** with charset-normalizer or chardet. ftfy goal is to repair Unicode string whereas charset-normalizer to convert raw file in unknown encoding to unicode. + +## 🍰 How + + - Discard all charset encoding table that could not fit the binary content. + - Measure noise, or the mess once opened (by chunks) with a corresponding charset encoding. + - Extract matches with the lowest mess detected. + - Additionally, we measure coherence / probe for a language. + +**Wait a minute**, what is noise/mess and coherence according to **YOU ?** + +*Noise :* I opened hundred of text files, **written by humans**, with the wrong encoding table. **I observed**, then +**I established** some ground rules about **what is obvious** when **it seems like** a mess (aka. defining noise in rendered text). + I know that my interpretation of what is noise is probably incomplete, feel free to contribute in order to + improve or rewrite it. + +*Coherence :* For each language there is on earth, we have computed ranked letter appearance occurrences (the best we can). So I thought +that intel is worth something here. So I use those records against decoded text to check if I can detect intelligent design. + +## ⚡ Known limitations + + - Language detection is unreliable when text contains two or more languages sharing identical letters. (eg. HTML (english tags) + Turkish content (Sharing Latin characters)) + - Every charset detector heavily depends on sufficient content. In common cases, do not bother run detection on very tiny content. + +## ⚠️ About Python EOLs + +**If you are running:** + +- Python >=2.7,<3.5: Unsupported +- Python 3.5: charset-normalizer < 2.1 +- Python 3.6: charset-normalizer < 3.1 +- Python 3.7: charset-normalizer < 4.0 + +Upgrade your Python interpreter as soon as possible. + +## 👤 Contributing + +Contributions, issues and feature requests are very much welcome.
+Feel free to check [issues page](https://github.com/ousret/charset_normalizer/issues) if you want to contribute. + +## 📝 License + +Copyright © [Ahmed TAHRI @Ousret](https://github.com/Ousret).
+This project is [MIT](https://github.com/Ousret/charset_normalizer/blob/master/LICENSE) licensed. + +Characters frequencies used in this project © 2012 [Denny Vrandečić](http://simia.net/letters/) + +## 💼 For Enterprise + +Professional support for charset-normalizer is available as part of the [Tidelift +Subscription][1]. Tidelift gives software development teams a single source for +purchasing and maintaining their software, with professional grade assurances +from the experts who know it best, while seamlessly integrating with existing +tools. + +[1]: https://tidelift.com/subscription/pkg/pypi-charset-normalizer?utm_source=pypi-charset-normalizer&utm_medium=readme + +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7297/badge)](https://www.bestpractices.dev/projects/7297) + +# Changelog +All notable changes to charset-normalizer will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [3.4.2](https://github.com/Ousret/charset_normalizer/compare/3.4.1...3.4.2) (2025-05-02) + +### Fixed +- Addressed the DeprecationWarning in our CLI regarding `argparse.FileType` by backporting the target class into the package. (#591) +- Improved the overall reliability of the detector with CJK Ideographs. (#605) (#587) + +### Changed +- Optional mypyc compilation upgraded to version 1.15 for Python >= 3.8 + +## [3.4.1](https://github.com/Ousret/charset_normalizer/compare/3.4.0...3.4.1) (2024-12-24) + +### Changed +- Project metadata are now stored using `pyproject.toml` instead of `setup.cfg` using setuptools as the build backend. +- Enforce annotation delayed loading for a simpler and consistent types in the project. +- Optional mypyc compilation upgraded to version 1.14 for Python >= 3.8 + +### Added +- pre-commit configuration. +- noxfile. + +### Removed +- `build-requirements.txt` as per using `pyproject.toml` native build configuration. +- `bin/integration.py` and `bin/serve.py` in favor of downstream integration test (see noxfile). +- `setup.cfg` in favor of `pyproject.toml` metadata configuration. +- Unused `utils.range_scan` function. + +### Fixed +- Converting content to Unicode bytes may insert `utf_8` instead of preferred `utf-8`. (#572) +- Deprecation warning "'count' is passed as positional argument" when converting to Unicode bytes on Python 3.13+ + +## [3.4.0](https://github.com/Ousret/charset_normalizer/compare/3.3.2...3.4.0) (2024-10-08) + +### Added +- Argument `--no-preemptive` in the CLI to prevent the detector to search for hints. +- Support for Python 3.13 (#512) + +### Fixed +- Relax the TypeError exception thrown when trying to compare a CharsetMatch with anything else than a CharsetMatch. +- Improved the general reliability of the detector based on user feedbacks. (#520) (#509) (#498) (#407) (#537) +- Declared charset in content (preemptive detection) not changed when converting to utf-8 bytes. (#381) + +## [3.3.2](https://github.com/Ousret/charset_normalizer/compare/3.3.1...3.3.2) (2023-10-31) + +### Fixed +- Unintentional memory usage regression when using large payload that match several encoding (#376) +- Regression on some detection case showcased in the documentation (#371) + +### Added +- Noise (md) probe that identify malformed arabic representation due to the presence of letters in isolated form (credit to my wife) + +## [3.3.1](https://github.com/Ousret/charset_normalizer/compare/3.3.0...3.3.1) (2023-10-22) + +### Changed +- Optional mypyc compilation upgraded to version 1.6.1 for Python >= 3.8 +- Improved the general detection reliability based on reports from the community + +## [3.3.0](https://github.com/Ousret/charset_normalizer/compare/3.2.0...3.3.0) (2023-09-30) + +### Added +- Allow to execute the CLI (e.g. normalizer) through `python -m charset_normalizer.cli` or `python -m charset_normalizer` +- Support for 9 forgotten encoding that are supported by Python but unlisted in `encoding.aliases` as they have no alias (#323) + +### Removed +- (internal) Redundant utils.is_ascii function and unused function is_private_use_only +- (internal) charset_normalizer.assets is moved inside charset_normalizer.constant + +### Changed +- (internal) Unicode code blocks in constants are updated using the latest v15.0.0 definition to improve detection +- Optional mypyc compilation upgraded to version 1.5.1 for Python >= 3.8 + +### Fixed +- Unable to properly sort CharsetMatch when both chaos/noise and coherence were close due to an unreachable condition in \_\_lt\_\_ (#350) + +## [3.2.0](https://github.com/Ousret/charset_normalizer/compare/3.1.0...3.2.0) (2023-06-07) + +### Changed +- Typehint for function `from_path` no longer enforce `PathLike` as its first argument +- Minor improvement over the global detection reliability + +### Added +- Introduce function `is_binary` that relies on main capabilities, and optimized to detect binaries +- Propagate `enable_fallback` argument throughout `from_bytes`, `from_path`, and `from_fp` that allow a deeper control over the detection (default True) +- Explicit support for Python 3.12 + +### Fixed +- Edge case detection failure where a file would contain 'very-long' camel cased word (Issue #289) + +## [3.1.0](https://github.com/Ousret/charset_normalizer/compare/3.0.1...3.1.0) (2023-03-06) + +### Added +- Argument `should_rename_legacy` for legacy function `detect` and disregard any new arguments without errors (PR #262) + +### Removed +- Support for Python 3.6 (PR #260) + +### Changed +- Optional speedup provided by mypy/c 1.0.1 + +## [3.0.1](https://github.com/Ousret/charset_normalizer/compare/3.0.0...3.0.1) (2022-11-18) + +### Fixed +- Multi-bytes cutter/chunk generator did not always cut correctly (PR #233) + +### Changed +- Speedup provided by mypy/c 0.990 on Python >= 3.7 + +## [3.0.0](https://github.com/Ousret/charset_normalizer/compare/2.1.1...3.0.0) (2022-10-20) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it +- Sphinx warnings when generating the documentation + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [3.0.0rc1](https://github.com/Ousret/charset_normalizer/compare/3.0.0b2...3.0.0rc1) (2022-10-18) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' + +## [3.0.0b2](https://github.com/Ousret/charset_normalizer/compare/3.0.0b1...3.0.0b2) (2022-08-21) + +### Added +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Removed +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) + +### Fixed +- Sphinx warnings when generating the documentation + +## [3.0.0b1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...3.0.0b1) (2022-08-15) + +### Changed +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Removed +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [2.1.1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...2.1.1) (2022-08-19) + +### Deprecated +- Function `normalize` scheduled for removal in 3.0 + +### Changed +- Removed useless call to decode in fn is_unprintable (#206) + +### Fixed +- Third-party library (i18n xgettext) crashing not recognizing utf_8 (PEP 263) with underscore from [@aleksandernovikov](https://github.com/aleksandernovikov) (#204) + +## [2.1.0](https://github.com/Ousret/charset_normalizer/compare/2.0.12...2.1.0) (2022-06-19) + +### Added +- Output the Unicode table version when running the CLI with `--version` (PR #194) + +### Changed +- Re-use decoded buffer for single byte character sets from [@nijel](https://github.com/nijel) (PR #175) +- Fixing some performance bottlenecks from [@deedy5](https://github.com/deedy5) (PR #183) + +### Fixed +- Workaround potential bug in cpython with Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space (PR #175) +- CLI default threshold aligned with the API threshold from [@oleksandr-kuzmenko](https://github.com/oleksandr-kuzmenko) (PR #181) + +### Removed +- Support for Python 3.5 (PR #192) + +### Deprecated +- Use of backport unicodedata from `unicodedata2` as Python is quickly catching up, scheduled for removal in 3.0 (PR #194) + +## [2.0.12](https://github.com/Ousret/charset_normalizer/compare/2.0.11...2.0.12) (2022-02-12) + +### Fixed +- ASCII miss-detection on rare cases (PR #170) + +## [2.0.11](https://github.com/Ousret/charset_normalizer/compare/2.0.10...2.0.11) (2022-01-30) + +### Added +- Explicit support for Python 3.11 (PR #164) + +### Changed +- The logging behavior have been completely reviewed, now using only TRACE and DEBUG levels (PR #163 #165) + +## [2.0.10](https://github.com/Ousret/charset_normalizer/compare/2.0.9...2.0.10) (2022-01-04) + +### Fixed +- Fallback match entries might lead to UnicodeDecodeError for large bytes sequence (PR #154) + +### Changed +- Skipping the language-detection (CD) on ASCII (PR #155) + +## [2.0.9](https://github.com/Ousret/charset_normalizer/compare/2.0.8...2.0.9) (2021-12-03) + +### Changed +- Moderating the logging impact (since 2.0.8) for specific environments (PR #147) + +### Fixed +- Wrong logging level applied when setting kwarg `explain` to True (PR #146) + +## [2.0.8](https://github.com/Ousret/charset_normalizer/compare/2.0.7...2.0.8) (2021-11-24) +### Changed +- Improvement over Vietnamese detection (PR #126) +- MD improvement on trailing data and long foreign (non-pure latin) data (PR #124) +- Efficiency improvements in cd/alphabet_languages from [@adbar](https://github.com/adbar) (PR #122) +- call sum() without an intermediary list following PEP 289 recommendations from [@adbar](https://github.com/adbar) (PR #129) +- Code style as refactored by Sourcery-AI (PR #131) +- Minor adjustment on the MD around european words (PR #133) +- Remove and replace SRTs from assets / tests (PR #139) +- Initialize the library logger with a `NullHandler` by default from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Setting kwarg `explain` to True will add provisionally (bounded to function lifespan) a specific stream handler (PR #135) + +### Fixed +- Fix large (misleading) sequence giving UnicodeDecodeError (PR #137) +- Avoid using too insignificant chunk (PR #137) + +### Added +- Add and expose function `set_logging_handler` to configure a specific StreamHandler from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Add `CHANGELOG.md` entries, format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) (PR #141) + +## [2.0.7](https://github.com/Ousret/charset_normalizer/compare/2.0.6...2.0.7) (2021-10-11) +### Added +- Add support for Kazakh (Cyrillic) language detection (PR #109) + +### Changed +- Further, improve inferring the language from a given single-byte code page (PR #112) +- Vainly trying to leverage PEP263 when PEP3120 is not supported (PR #116) +- Refactoring for potential performance improvements in loops from [@adbar](https://github.com/adbar) (PR #113) +- Various detection improvement (MD+CD) (PR #117) + +### Removed +- Remove redundant logging entry about detected language(s) (PR #115) + +### Fixed +- Fix a minor inconsistency between Python 3.5 and other versions regarding language detection (PR #117 #102) + +## [2.0.6](https://github.com/Ousret/charset_normalizer/compare/2.0.5...2.0.6) (2021-09-18) +### Fixed +- Unforeseen regression with the loss of the backward-compatibility with some older minor of Python 3.5.x (PR #100) +- Fix CLI crash when using --minimal output in certain cases (PR #103) + +### Changed +- Minor improvement to the detection efficiency (less than 1%) (PR #106 #101) + +## [2.0.5](https://github.com/Ousret/charset_normalizer/compare/2.0.4...2.0.5) (2021-09-14) +### Changed +- The project now comply with: flake8, mypy, isort and black to ensure a better overall quality (PR #81) +- The BC-support with v1.x was improved, the old staticmethods are restored (PR #82) +- The Unicode detection is slightly improved (PR #93) +- Add syntax sugar \_\_bool\_\_ for results CharsetMatches list-container (PR #91) + +### Removed +- The project no longer raise warning on tiny content given for detection, will be simply logged as warning instead (PR #92) + +### Fixed +- In some rare case, the chunks extractor could cut in the middle of a multi-byte character and could mislead the mess detection (PR #95) +- Some rare 'space' characters could trip up the UnprintablePlugin/Mess detection (PR #96) +- The MANIFEST.in was not exhaustive (PR #78) + +## [2.0.4](https://github.com/Ousret/charset_normalizer/compare/2.0.3...2.0.4) (2021-07-30) +### Fixed +- The CLI no longer raise an unexpected exception when no encoding has been found (PR #70) +- Fix accessing the 'alphabets' property when the payload contains surrogate characters (PR #68) +- The logger could mislead (explain=True) on detected languages and the impact of one MBCS match (PR #72) +- Submatch factoring could be wrong in rare edge cases (PR #72) +- Multiple files given to the CLI were ignored when publishing results to STDOUT. (After the first path) (PR #72) +- Fix line endings from CRLF to LF for certain project files (PR #67) + +### Changed +- Adjust the MD to lower the sensitivity, thus improving the global detection reliability (PR #69 #76) +- Allow fallback on specified encoding if any (PR #71) + +## [2.0.3](https://github.com/Ousret/charset_normalizer/compare/2.0.2...2.0.3) (2021-07-16) +### Changed +- Part of the detection mechanism has been improved to be less sensitive, resulting in more accurate detection results. Especially ASCII. (PR #63) +- According to the community wishes, the detection will fall back on ASCII or UTF-8 in a last-resort case. (PR #64) + +## [2.0.2](https://github.com/Ousret/charset_normalizer/compare/2.0.1...2.0.2) (2021-07-15) +### Fixed +- Empty/Too small JSON payload miss-detection fixed. Report from [@tseaver](https://github.com/tseaver) (PR #59) + +### Changed +- Don't inject unicodedata2 into sys.modules from [@akx](https://github.com/akx) (PR #57) + +## [2.0.1](https://github.com/Ousret/charset_normalizer/compare/2.0.0...2.0.1) (2021-07-13) +### Fixed +- Make it work where there isn't a filesystem available, dropping assets frequencies.json. Report from [@sethmlarson](https://github.com/sethmlarson). (PR #55) +- Using explain=False permanently disable the verbose output in the current runtime (PR #47) +- One log entry (language target preemptive) was not show in logs when using explain=True (PR #47) +- Fix undesired exception (ValueError) on getitem of instance CharsetMatches (PR #52) + +### Changed +- Public function normalize default args values were not aligned with from_bytes (PR #53) + +### Added +- You may now use charset aliases in cp_isolation and cp_exclusion arguments (PR #47) + +## [2.0.0](https://github.com/Ousret/charset_normalizer/compare/1.4.1...2.0.0) (2021-07-02) +### Changed +- 4x to 5 times faster than the previous 1.4.0 release. At least 2x faster than Chardet. +- Accent has been made on UTF-8 detection, should perform rather instantaneous. +- The backward compatibility with Chardet has been greatly improved. The legacy detect function returns an identical charset name whenever possible. +- The detection mechanism has been slightly improved, now Turkish content is detected correctly (most of the time) +- The program has been rewritten to ease the readability and maintainability. (+Using static typing)+ +- utf_7 detection has been reinstated. + +### Removed +- This package no longer require anything when used with Python 3.5 (Dropped cached_property) +- Removed support for these languages: Catalan, Esperanto, Kazakh, Baque, Volapük, Azeri, Galician, Nynorsk, Macedonian, and Serbocroatian. +- The exception hook on UnicodeDecodeError has been removed. + +### Deprecated +- Methods coherence_non_latin, w_counter, chaos_secondary_pass of the class CharsetMatch are now deprecated and scheduled for removal in v3.0 + +### Fixed +- The CLI output used the relative path of the file(s). Should be absolute. + +## [1.4.1](https://github.com/Ousret/charset_normalizer/compare/1.4.0...1.4.1) (2021-05-28) +### Fixed +- Logger configuration/usage no longer conflict with others (PR #44) + +## [1.4.0](https://github.com/Ousret/charset_normalizer/compare/1.3.9...1.4.0) (2021-05-21) +### Removed +- Using standard logging instead of using the package loguru. +- Dropping nose test framework in favor of the maintained pytest. +- Choose to not use dragonmapper package to help with gibberish Chinese/CJK text. +- Require cached_property only for Python 3.5 due to constraint. Dropping for every other interpreter version. +- Stop support for UTF-7 that does not contain a SIG. +- Dropping PrettyTable, replaced with pure JSON output in CLI. + +### Fixed +- BOM marker in a CharsetNormalizerMatch instance could be False in rare cases even if obviously present. Due to the sub-match factoring process. +- Not searching properly for the BOM when trying utf32/16 parent codec. + +### Changed +- Improving the package final size by compressing frequencies.json. +- Huge improvement over the larges payload. + +### Added +- CLI now produces JSON consumable output. +- Return ASCII if given sequences fit. Given reasonable confidence. + +## [1.3.9](https://github.com/Ousret/charset_normalizer/compare/1.3.8...1.3.9) (2021-05-13) + +### Fixed +- In some very rare cases, you may end up getting encode/decode errors due to a bad bytes payload (PR #40) + +## [1.3.8](https://github.com/Ousret/charset_normalizer/compare/1.3.7...1.3.8) (2021-05-12) + +### Fixed +- Empty given payload for detection may cause an exception if trying to access the `alphabets` property. (PR #39) + +## [1.3.7](https://github.com/Ousret/charset_normalizer/compare/1.3.6...1.3.7) (2021-05-12) + +### Fixed +- The legacy detect function should return UTF-8-SIG if sig is present in the payload. (PR #38) + +## [1.3.6](https://github.com/Ousret/charset_normalizer/compare/1.3.5...1.3.6) (2021-02-09) + +### Changed +- Amend the previous release to allow prettytable 2.0 (PR #35) + +## [1.3.5](https://github.com/Ousret/charset_normalizer/compare/1.3.4...1.3.5) (2021-02-08) + +### Fixed +- Fix error while using the package with a python pre-release interpreter (PR #33) + +### Changed +- Dependencies refactoring, constraints revised. + +### Added +- Add python 3.9 and 3.10 to the supported interpreters + +MIT License + +Copyright (c) 2025 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/RECORD b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/RECORD new file mode 100644 index 0000000..6a1ae3a --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/RECORD @@ -0,0 +1,35 @@ +../../Scripts/normalizer.exe,sha256=BWIYbM0Lh6p1Fr4cs5jUnwYyljIEwWOoCvc_9ppuB6o,108452 +charset_normalizer-3.4.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +charset_normalizer-3.4.2.dist-info/METADATA,sha256=WneNNyl9QvsRZYzK1FeEC6Wwag4iIFoTAoevPgpZFTY,36474 +charset_normalizer-3.4.2.dist-info/RECORD,, +charset_normalizer-3.4.2.dist-info/WHEEL,sha256=PqPfOxA7mGIdQ4POxRbyNp_0DDxNzJM1gpSaGcLLjlo,101 +charset_normalizer-3.4.2.dist-info/entry_points.txt,sha256=8C-Y3iXIfyXQ83Tpir2B8t-XLJYpxF5xbb38d_js-h4,65 +charset_normalizer-3.4.2.dist-info/licenses/LICENSE,sha256=GFd0hdNwTxpHne2OVzwJds_tMV_S_ReYP6mI2kwvcNE,1092 +charset_normalizer-3.4.2.dist-info/top_level.txt,sha256=7ASyzePr8_xuZWJsnqJjIBtyV8vhEo0wBCv1MPRRi3Q,19 +charset_normalizer/__init__.py,sha256=0NT8MHi7SKq3juMqYfOdrkzjisK0L73lneNHH4qaUAs,1638 +charset_normalizer/__main__.py,sha256=2sj_BS6H0sU25C1bMqz9DVwa6kOK9lchSEbSU-_iu7M,115 +charset_normalizer/__pycache__/__init__.cpython-312.pyc,, +charset_normalizer/__pycache__/__main__.cpython-312.pyc,, +charset_normalizer/__pycache__/api.cpython-312.pyc,, +charset_normalizer/__pycache__/cd.cpython-312.pyc,, +charset_normalizer/__pycache__/constant.cpython-312.pyc,, +charset_normalizer/__pycache__/legacy.cpython-312.pyc,, +charset_normalizer/__pycache__/md.cpython-312.pyc,, +charset_normalizer/__pycache__/models.cpython-312.pyc,, +charset_normalizer/__pycache__/utils.cpython-312.pyc,, +charset_normalizer/__pycache__/version.cpython-312.pyc,, +charset_normalizer/api.py,sha256=2a0p2Gnhbdo9O6C04CNxTSN23fIbgOF20nxb0pWPNFM,23285 +charset_normalizer/cd.py,sha256=uq8nVxRpR6Guc16ACvOWtL8KO3w7vYaCh8hHisuOyTg,12917 +charset_normalizer/cli/__init__.py,sha256=d9MUx-1V_qD3x9igIy4JT4oC5CU0yjulk7QyZWeRFhg,144 +charset_normalizer/cli/__main__.py,sha256=-pdJCyPywouPyFsC8_eTSgTmvh1YEvgjsvy1WZ0XjaA,13027 +charset_normalizer/cli/__pycache__/__init__.cpython-312.pyc,, +charset_normalizer/cli/__pycache__/__main__.cpython-312.pyc,, +charset_normalizer/constant.py,sha256=mCJmYzpBU27Ut9kiNWWoBbhhxQ-aRVw3K7LSwoFwBGI,44728 +charset_normalizer/legacy.py,sha256=NgK-8ZQa_M9FHgQjdNSiYzMaB332QGuElZSfCf2y2sQ,2351 +charset_normalizer/md.cp312-win_amd64.pyd,sha256=Rl7qVeM9DRj8hZuyoyQjaRKsZ6zkpojb7Nx1nNTbjr8,10752 +charset_normalizer/md.py,sha256=LSuW2hNgXSgF7JGdRapLAHLuj6pABHiP85LTNAYmu7c,20780 +charset_normalizer/md__mypyc.cp312-win_amd64.pyd,sha256=WXIlB5bZkRXY2k4r70Pmyf7eLRw6zLib-dXXkRIrOZA,125952 +charset_normalizer/models.py,sha256=ZR2PE-fqf6dASZfqdE5Uhkmr0o1MciSdXOjuNqwkmvg,12754 +charset_normalizer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer/utils.py,sha256=XtWIQeOuz7cnGebMzyi4Vvi1JtA84QBSIeR9PDzF7pw,12584 +charset_normalizer/version.py,sha256=wtpyUZ7M57rCLclP3QjzRD0Nj2hvnMOzLZI-vwfTdWs,123 diff --git a/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/WHEEL b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/WHEEL new file mode 100644 index 0000000..9726646 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.1.0) +Root-Is-Purelib: false +Tag: cp312-cp312-win_amd64 + diff --git a/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/entry_points.txt b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/entry_points.txt new file mode 100644 index 0000000..ec92012 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +normalizer = charset_normalizer:cli.cli_detect diff --git a/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/licenses/LICENSE b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/licenses/LICENSE new file mode 100644 index 0000000..9725772 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/top_level.txt b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/top_level.txt new file mode 100644 index 0000000..66958f0 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer-3.4.2.dist-info/top_level.txt @@ -0,0 +1 @@ +charset_normalizer diff --git a/.venv/Lib/site-packages/charset_normalizer/__init__.py b/.venv/Lib/site-packages/charset_normalizer/__init__.py new file mode 100644 index 0000000..0d3a379 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/__init__.py @@ -0,0 +1,48 @@ +""" +Charset-Normalizer +~~~~~~~~~~~~~~ +The Real First Universal Charset Detector. +A library that helps you read text from an unknown charset encoding. +Motivated by chardet, This package is trying to resolve the issue by taking a new approach. +All IANA character set names for which the Python core library provides codecs are supported. + +Basic usage: + >>> from charset_normalizer import from_bytes + >>> results = from_bytes('Bсеки човек има право на образование. Oбразованието!'.encode('utf_8')) + >>> best_guess = results.best() + >>> str(best_guess) + 'Bсеки човек има право на образование. Oбразованието!' + +Others methods and usages are available - see the full documentation +at . +:copyright: (c) 2021 by Ahmed TAHRI +:license: MIT, see LICENSE for more details. +""" + +from __future__ import annotations + +import logging + +from .api import from_bytes, from_fp, from_path, is_binary +from .legacy import detect +from .models import CharsetMatch, CharsetMatches +from .utils import set_logging_handler +from .version import VERSION, __version__ + +__all__ = ( + "from_fp", + "from_path", + "from_bytes", + "is_binary", + "detect", + "CharsetMatch", + "CharsetMatches", + "__version__", + "VERSION", + "set_logging_handler", +) + +# Attach a NullHandler to the top level logger by default +# https://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library + +logging.getLogger("charset_normalizer").addHandler(logging.NullHandler()) diff --git a/.venv/Lib/site-packages/charset_normalizer/__main__.py b/.venv/Lib/site-packages/charset_normalizer/__main__.py new file mode 100644 index 0000000..e0e76f7 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/__main__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from .cli import cli_detect + +if __name__ == "__main__": + cli_detect() diff --git a/.venv/Lib/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee117b73ddc8f99279bab4c6c1fa627dc6c48656 GIT binary patch literal 1797 zcmcIk&1)n@6tA9{%p^TY)+7f(5KDwjMw7{!9F$=VWb;7=@t_u`-6e;~+SqOcf5;~(IqhaiE5WeEOtKQM_Tlw=uj;+`dtdeY z*qDys`_8I4ts+7%lrZ=T8Kb?5f%z8M$UwGc=a}Z@jGU(UJj)w-yI>Eof;VIgc}1h> zm5h=%Yz)JB!7j29PdD_GFR`*WYK(eg##ky3vvKd9aWCaZ*jwI&G2u-blOXHHR1VSn z(aZqI?cXjx(K6}iqg(BOORZ)H((V6ApB>n0sGF~hMvwCd=_?N5g37t=fO~|wUsA4r z^{-Dqa46oRgyD6U3yBYXw?nxAxo?VBsic++cvWA<%x!YQyI49zVu!L&;BF9MPKk{r z{X*gv4?IkK9Qnt7aN=Vti;t;q1-9$AtNKN zci}dc0r(aH>re=(4n#ziog~NVm|<U=oHrAeI9F8N>DE?d8-ju_WaflE*&rsKBj& z;}gfVoYd8BS2_XU3pl;W3fy#D8?FLYY6(o>OhjQAaDZFY^+!aw7LEj9tmzozrKP1T z;eM8;e^X`bdde(4Hk(~Z#lQgaiI^1lvp3FWrXTgbj-SRq$LF~BO?(mms019J$G^m9 zIR3478lQpg0>{59`6B)y)%=v+JOkx<{IrU9{!86^9ACUYT}|sxXQm_BGC!TJR0e38 zR7kTOQK10!p;eV~=H`l!eCGCAs{X&>>iQ0}n+xnw=#mYc@a?SbSyM=dxQsLz#dFZ$ zY&KdEG~W)a$fLd_(hYnaX7r&WWhiR%^KDl;QL}0V-uzA^IF<8vo3>QdYgQ0;x!ZPR z4bNB=yzpS*V>NKgjt3Kau)MKX*K5qRs4r*@Z`BWGQvjRwm9_2twRFZjHBvCt08v!+ zmufjHT2dyyA7r@1_Di+pl|rJcb;-sfDW!ZXOh!^BB+^MnTwyj{A2tp=6u~DfVkMW1 z_E&R@NXtpa-;}6;n^Q3KCJWka*b&o#+A+$(tn~TX-hO>&J1Lu{+Hok6X;yR&4*^IBIAmTDKZKqnY>%O%G*iFG@<^cndqk3ie$v8X>xdkP*UoHN%ACg zvnaPgRlKCgXq#BwG;UJ#VE>r7jh_rf5~`U6FZHo!coZ@fnJ@;CN7__wLgJJsI_9bmj_2sY0+3GLN4#taYx{F zM#OPr170Y2R91tdpi%8mzth-sn+@SgI@f=^MB{F4=1tS}U1^%tu$v4&gzO?lm-u@C zLk)sB1ah6%H0`h4lva5CA<{m0fyVztwM$fchTeIO9y~+$pQFlkpr7$X!;5*^w2`QxPJD@)#7?@as6^};}2Bl?}F!l02WDR8~^|S literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/__pycache__/__main__.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef2481af5372518a8cb4571bc56981d4673b8855 GIT binary patch literal 372 zcmXv}Jxc>Y5S`85Nl1vjif~w&!q`~^5yTJ_f&_w1SXkEE(Om9h!|vuF=>`9R|3DCb zik+PYf*=;@0_m*WBW8-3_h#M8n}24+au%ZcS$x;Sp%{s~EPA}-NlXp1W6 zsfaSpubX~Ri+Yh}Z1#ef^ Go#GGq_GrTZ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac3983b1f5f401a6e68b4f18d7a84f24efb2415a GIT binary patch literal 18158 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 F{{SbZITZi^ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd600f64d7f9a76c4eb8e3fd6c575959b7aaaedd GIT binary patch literal 40827 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f15e7fad8e5fb20b8b228bef9b14f5fd5f8aafd GIT binary patch literal 2797 zcmZ`*U2Gf25#Hk+$s+MY=Xdjj*YZp=o{X-0*77q#O@D>oak%0`!Lbg09waw$YQzekH_mlHa2{j>~m!x(>_5{NAOT^LcIhYJU3q zwYfKZ`HIs3A!S3S&R0^nB3+@bt(6*_FklT$U%j$0lV8%NZ!9e27w2!x&E)5%^T0_h zHR{BeG0VIhx)lC9{z->pFusilDo6$@#Fil@L^fit!>l0#r36$QsDz*z$(-t^7YOq# zE_622j8ojgbFM>tl{jVBFrBI|!>q4bx>NP^DrsgamaFqfEM0b;ifMo_nae4@s%egH z6HW8enr6F(X9+r^X=|QtbvS*R_Edm0?IUz3w(%c6Jy~34gtFqIZWm_=Tjg%OxCqMe zJatz{nX}@o&iIx2Yj~aESxaZD#jARGl~6q6uDHdCb>gfSe`J=5%;aRe4lHn(70Wf9 zGQu^-rM7OFO+qhNq^g%26ZHnofa&1Z3j`a0-!D=7#K;46q-N$9w&a%l^Gr)#8Se5T%8`utmIC}Uz<)lP?e<1u^)->m zXLDh7aw`t2`wSH%M!M@FstHIF*hqlXP)jjVtD~T=AEaN1WV$_#gG9OsB?CT5Mvo^X z6^@m9jfyeluFT(ABnBqNQXiTX4M$6--ooA8=z1e_Vdq3kiQ z5gZ*y>{fzt)r4xt0x~e1PdKL&_!`Ho=6aTaO9b04<5mNg;1B@P7~^Hvt^*{MEHV+O z&UcPC7N-(#n3jcYotJBPy5o(z;ouEwa!`#oYQ!PyaL(#3V}TAlI_GliI$gg4J)Xn7 zdfla*fD;XD@?2nSsb(@<@tm^2L=3b+>XejqA*MUVQ*L_T~W(9FD+_GmACxlyEacN+%;!y}1#ij!@urDw>0l3C-9dOJ66EKY_vE6mu znn(vrChI!Y?I5BWM08SQ1}{P6!x)n_Pk@~8jcn?+>2%`QSr!Ifqa*;iFq5ESG0evX z0b@acIysKA$pX`XssH-}i?A1yLQ1K~t3fD)ea$n$ci1?a3Uo9_J1{rig*j}Pi~`b8 zh%&7PVPg0dUm9zkeFZ`j72I)Rpa`MdMsv;Y<~Qp=L7d!@0N$YJ;OYrt!7#*51VyJ* zhip_`K8ff2QZ0f;2Ko}EhPfEJ2vCTAt)~M z<&tYtfvmZtuZr~|!~V%{^)Za!& zBS`JpxpeDikJL;%d+9#=bn_3Je^x&G@n7Eh{H-tEe(;0E2d^#dU0U8-rh6H7B*#+e zBNb(ac55G;{y4vfzxS}`;-l=Tc6JID{q&3M-qek~%>1Do>q{S?P*^>&JNoXmhske0 z8W_Ga{QIdL^`9_x8E&n=tLOIBa}U&)wy!)n`@)^Kch-K>{8jV5vVFBJhIiLKX@1oF z)6Fk0%!Zu5{t!4XzV!b1?!fNa?Tz1V{O;`irR_HlRHUZ1x9$wT|C29IkKOP8bnuUZ zZS}d`*?sl-9ra1_%pGq(`SRW2hp%4W&s~2skh?p&KX7qJJxHLzv+a{3?IHYiQpqF^ zkP=S>*XLYLn$4;7I}j)OviO~cpRbf$*P`OxLdDJKCn(__b%Gl?*C$>P6%Quh6+y*4 z>L(!AfJjt1UZIMI6il(Oj%2-VhP9vyn1J%(LFWDbaHU2zIdPbHn^Ez)3?Bd@gH&8s z;Y}Z8SA9Kvzz&KBTzAWM?;7`^y!^sUF}^uN%V$7t;91XB7RE9Z|CIeu>U l<`IJKAd4P;F9|T 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..313939f805b01843213bd0077f6ae1d51b982433 GIT binary patch literal 17140 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%0DcyUXA_2i2TsQQl^-2Q9^_`&B^?% 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c9222e06211e4f939201048be6de6469feec0c3 GIT binary patch literal 13727 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 Ce;;cA literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b792d22d7cc28f52f6e7426796c466a4753533c3 GIT binary patch literal 399 zcmYLFO-sW-5X~k{TN5f?dg>*hmmZ9uo{9+44=hM67CnT8WldI_HpzzFO|bQ%ze0b3 zApR6j;z7wt@Z?P?UOc&>73VPX-n?PvF^`psp~$#%8^cRk-*fU8+8lscLxLN{Qyj$u zo*IBqbyT2unxheIwDdRrU%kQum@`M8==I`s#b{r|loKl@jQcb)K61GZG80D;6 CharsetMatches: + """ + Given a raw bytes sequence, return the best possibles charset usable to render str objects. + If there is no results, it is a strong indicator that the source is binary/not text. + By default, the process will extract 5 blocks of 512o each to assess the mess and coherence of a given sequence. + And will give up a particular code page after 20% of measured mess. Those criteria are customizable at will. + + The preemptive behavior DOES NOT replace the traditional detection workflow, it prioritize a particular code page + but never take it for granted. Can improve the performance. + + You may want to focus your attention to some code page or/and not others, use cp_isolation and cp_exclusion for that + purpose. + + This function will strip the SIG in the payload/sequence every time except on UTF-16, UTF-32. + By default the library does not setup any handler other than the NullHandler, if you choose to set the 'explain' + toggle to True it will alter the logger configuration to add a StreamHandler that is suitable for debugging. + Custom logging format and handler can be set manually. + """ + + if not isinstance(sequences, (bytearray, bytes)): + raise TypeError( + "Expected object of type bytes or bytearray, got: {}".format( + type(sequences) + ) + ) + + if explain: + previous_logger_level: int = logger.level + logger.addHandler(explain_handler) + logger.setLevel(TRACE) + + length: int = len(sequences) + + if length == 0: + logger.debug("Encoding detection on empty bytes, assuming utf_8 intention.") + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level or logging.WARNING) + return CharsetMatches([CharsetMatch(sequences, "utf_8", 0.0, False, [], "")]) + + if cp_isolation is not None: + logger.log( + TRACE, + "cp_isolation is set. use this flag for debugging purpose. " + "limited list of encoding allowed : %s.", + ", ".join(cp_isolation), + ) + cp_isolation = [iana_name(cp, False) for cp in cp_isolation] + else: + cp_isolation = [] + + if cp_exclusion is not None: + logger.log( + TRACE, + "cp_exclusion is set. use this flag for debugging purpose. " + "limited list of encoding excluded : %s.", + ", ".join(cp_exclusion), + ) + cp_exclusion = [iana_name(cp, False) for cp in cp_exclusion] + else: + cp_exclusion = [] + + if length <= (chunk_size * steps): + logger.log( + TRACE, + "override steps (%i) and chunk_size (%i) as content does not fit (%i byte(s) given) parameters.", + steps, + chunk_size, + length, + ) + steps = 1 + chunk_size = length + + if steps > 1 and length / steps < chunk_size: + chunk_size = int(length / steps) + + is_too_small_sequence: bool = len(sequences) < TOO_SMALL_SEQUENCE + is_too_large_sequence: bool = len(sequences) >= TOO_BIG_SEQUENCE + + if is_too_small_sequence: + logger.log( + TRACE, + "Trying to detect encoding from a tiny portion of ({}) byte(s).".format( + length + ), + ) + elif is_too_large_sequence: + logger.log( + TRACE, + "Using lazy str decoding because the payload is quite large, ({}) byte(s).".format( + length + ), + ) + + prioritized_encodings: list[str] = [] + + specified_encoding: str | None = ( + any_specified_encoding(sequences) if preemptive_behaviour else None + ) + + if specified_encoding is not None: + prioritized_encodings.append(specified_encoding) + logger.log( + TRACE, + "Detected declarative mark in sequence. Priority +1 given for %s.", + specified_encoding, + ) + + tested: set[str] = set() + tested_but_hard_failure: list[str] = [] + tested_but_soft_failure: list[str] = [] + + fallback_ascii: CharsetMatch | None = None + fallback_u8: CharsetMatch | None = None + fallback_specified: CharsetMatch | None = None + + results: CharsetMatches = CharsetMatches() + + early_stop_results: CharsetMatches = CharsetMatches() + + sig_encoding, sig_payload = identify_sig_or_bom(sequences) + + if sig_encoding is not None: + prioritized_encodings.append(sig_encoding) + logger.log( + TRACE, + "Detected a SIG or BOM mark on first %i byte(s). Priority +1 given for %s.", + len(sig_payload), + sig_encoding, + ) + + prioritized_encodings.append("ascii") + + if "utf_8" not in prioritized_encodings: + prioritized_encodings.append("utf_8") + + for encoding_iana in prioritized_encodings + IANA_SUPPORTED: + if cp_isolation and encoding_iana not in cp_isolation: + continue + + if cp_exclusion and encoding_iana in cp_exclusion: + continue + + if encoding_iana in tested: + continue + + tested.add(encoding_iana) + + decoded_payload: str | None = None + bom_or_sig_available: bool = sig_encoding == encoding_iana + strip_sig_or_bom: bool = bom_or_sig_available and should_strip_sig_or_bom( + encoding_iana + ) + + if encoding_iana in {"utf_16", "utf_32"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because it require a BOM. Will try some sub-encoder LE/BE.", + encoding_iana, + ) + continue + if encoding_iana in {"utf_7"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because detection is unreliable without BOM/SIG.", + encoding_iana, + ) + continue + + try: + is_multi_byte_decoder: bool = is_multi_byte_encoding(encoding_iana) + except (ModuleNotFoundError, ImportError): + logger.log( + TRACE, + "Encoding %s does not provide an IncrementalDecoder", + encoding_iana, + ) + continue + + try: + if is_too_large_sequence and is_multi_byte_decoder is False: + str( + ( + sequences[: int(50e4)] + if strip_sig_or_bom is False + else sequences[len(sig_payload) : int(50e4)] + ), + encoding=encoding_iana, + ) + else: + decoded_payload = str( + ( + sequences + if strip_sig_or_bom is False + else sequences[len(sig_payload) :] + ), + encoding=encoding_iana, + ) + except (UnicodeDecodeError, LookupError) as e: + if not isinstance(e, LookupError): + logger.log( + TRACE, + "Code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + similar_soft_failure_test: bool = False + + for encoding_soft_failed in tested_but_soft_failure: + if is_cp_similar(encoding_iana, encoding_soft_failed): + similar_soft_failure_test = True + break + + if similar_soft_failure_test: + logger.log( + TRACE, + "%s is deemed too similar to code page %s and was consider unsuited already. Continuing!", + encoding_iana, + encoding_soft_failed, + ) + continue + + r_ = range( + 0 if not bom_or_sig_available else len(sig_payload), + length, + int(length / steps), + ) + + multi_byte_bonus: bool = ( + is_multi_byte_decoder + and decoded_payload is not None + and len(decoded_payload) < length + ) + + if multi_byte_bonus: + logger.log( + TRACE, + "Code page %s is a multi byte encoding table and it appear that at least one character " + "was encoded using n-bytes.", + encoding_iana, + ) + + max_chunk_gave_up: int = int(len(r_) / 4) + + max_chunk_gave_up = max(max_chunk_gave_up, 2) + early_stop_count: int = 0 + lazy_str_hard_failure = False + + md_chunks: list[str] = [] + md_ratios = [] + + try: + for chunk in cut_sequence_chunks( + sequences, + encoding_iana, + r_, + chunk_size, + bom_or_sig_available, + strip_sig_or_bom, + sig_payload, + is_multi_byte_decoder, + decoded_payload, + ): + md_chunks.append(chunk) + + md_ratios.append( + mess_ratio( + chunk, + threshold, + explain is True and 1 <= len(cp_isolation) <= 2, + ) + ) + + if md_ratios[-1] >= threshold: + early_stop_count += 1 + + if (early_stop_count >= max_chunk_gave_up) or ( + bom_or_sig_available and strip_sig_or_bom is False + ): + break + except ( + UnicodeDecodeError + ) as e: # Lazy str loading may have missed something there + logger.log( + TRACE, + "LazyStr Loading: After MD chunk decode, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + early_stop_count = max_chunk_gave_up + lazy_str_hard_failure = True + + # We might want to check the sequence again with the whole content + # Only if initial MD tests passes + if ( + not lazy_str_hard_failure + and is_too_large_sequence + and not is_multi_byte_decoder + ): + try: + sequences[int(50e3) :].decode(encoding_iana, errors="strict") + except UnicodeDecodeError as e: + logger.log( + TRACE, + "LazyStr Loading: After final lookup, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + mean_mess_ratio: float = sum(md_ratios) / len(md_ratios) if md_ratios else 0.0 + if mean_mess_ratio >= threshold or early_stop_count >= max_chunk_gave_up: + tested_but_soft_failure.append(encoding_iana) + logger.log( + TRACE, + "%s was excluded because of initial chaos probing. Gave up %i time(s). " + "Computed mean chaos is %f %%.", + encoding_iana, + early_stop_count, + round(mean_mess_ratio * 100, ndigits=3), + ) + # Preparing those fallbacks in case we got nothing. + if ( + enable_fallback + and encoding_iana in ["ascii", "utf_8", specified_encoding] + and not lazy_str_hard_failure + ): + fallback_entry = CharsetMatch( + sequences, + encoding_iana, + threshold, + False, + [], + decoded_payload, + preemptive_declaration=specified_encoding, + ) + if encoding_iana == specified_encoding: + fallback_specified = fallback_entry + elif encoding_iana == "ascii": + fallback_ascii = fallback_entry + else: + fallback_u8 = fallback_entry + continue + + logger.log( + TRACE, + "%s passed initial chaos probing. Mean measured chaos is %f %%", + encoding_iana, + round(mean_mess_ratio * 100, ndigits=3), + ) + + if not is_multi_byte_decoder: + target_languages: list[str] = encoding_languages(encoding_iana) + else: + target_languages = mb_encoding_languages(encoding_iana) + + if target_languages: + logger.log( + TRACE, + "{} should target any language(s) of {}".format( + encoding_iana, str(target_languages) + ), + ) + + cd_ratios = [] + + # We shall skip the CD when its about ASCII + # Most of the time its not relevant to run "language-detection" on it. + if encoding_iana != "ascii": + for chunk in md_chunks: + chunk_languages = coherence_ratio( + chunk, + language_threshold, + ",".join(target_languages) if target_languages else None, + ) + + cd_ratios.append(chunk_languages) + + cd_ratios_merged = merge_coherence_ratios(cd_ratios) + + if cd_ratios_merged: + logger.log( + TRACE, + "We detected language {} using {}".format( + cd_ratios_merged, encoding_iana + ), + ) + + current_match = CharsetMatch( + sequences, + encoding_iana, + mean_mess_ratio, + bom_or_sig_available, + cd_ratios_merged, + ( + decoded_payload + if ( + is_too_large_sequence is False + or encoding_iana in [specified_encoding, "ascii", "utf_8"] + ) + else None + ), + preemptive_declaration=specified_encoding, + ) + + results.append(current_match) + + if ( + encoding_iana in [specified_encoding, "ascii", "utf_8"] + and mean_mess_ratio < 0.1 + ): + # If md says nothing to worry about, then... stop immediately! + if mean_mess_ratio == 0.0: + logger.debug( + "Encoding detection: %s is most likely the one.", + current_match.encoding, + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([current_match]) + + early_stop_results.append(current_match) + + if ( + len(early_stop_results) + and (specified_encoding is None or specified_encoding in tested) + and "ascii" in tested + and "utf_8" in tested + ): + probable_result: CharsetMatch = early_stop_results.best() # type: ignore[assignment] + logger.debug( + "Encoding detection: %s is most likely the one.", + probable_result.encoding, + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return CharsetMatches([probable_result]) + + if encoding_iana == sig_encoding: + logger.debug( + "Encoding detection: %s is most likely the one as we detected a BOM or SIG within " + "the beginning of the sequence.", + encoding_iana, + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([results[encoding_iana]]) + + if len(results) == 0: + if fallback_u8 or fallback_ascii or fallback_specified: + logger.log( + TRACE, + "Nothing got out of the detection process. Using ASCII/UTF-8/Specified fallback.", + ) + + if fallback_specified: + logger.debug( + "Encoding detection: %s will be used as a fallback match", + fallback_specified.encoding, + ) + results.append(fallback_specified) + elif ( + (fallback_u8 and fallback_ascii is None) + or ( + fallback_u8 + and fallback_ascii + and fallback_u8.fingerprint != fallback_ascii.fingerprint + ) + or (fallback_u8 is not None) + ): + logger.debug("Encoding detection: utf_8 will be used as a fallback match") + results.append(fallback_u8) + elif fallback_ascii: + logger.debug("Encoding detection: ascii will be used as a fallback match") + results.append(fallback_ascii) + + if results: + logger.debug( + "Encoding detection: Found %s as plausible (best-candidate) for content. With %i alternatives.", + results.best().encoding, # type: ignore + len(results) - 1, + ) + else: + logger.debug("Encoding detection: Unable to determine any suitable charset.") + + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return results + + +def from_fp( + fp: BinaryIO, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but using a file pointer that is already ready. + Will not close the file pointer. + """ + return from_bytes( + fp.read(), + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + enable_fallback, + ) + + +def from_path( + path: str | bytes | PathLike, # type: ignore[type-arg] + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but with one extra step. Opening and reading given file path in binary mode. + Can raise IOError. + """ + with open(path, "rb") as fp: + return from_fp( + fp, + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + enable_fallback, + ) + + +def is_binary( + fp_or_path_or_payload: PathLike | str | BinaryIO | bytes, # type: ignore[type-arg] + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = False, +) -> bool: + """ + Detect if the given input (file, bytes, or path) points to a binary file. aka. not a string. + Based on the same main heuristic algorithms and default kwargs at the sole exception that fallbacks match + are disabled to be stricter around ASCII-compatible but unlikely to be a string. + """ + if isinstance(fp_or_path_or_payload, (str, PathLike)): + guesses = from_path( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + elif isinstance( + fp_or_path_or_payload, + ( + bytes, + bytearray, + ), + ): + guesses = from_bytes( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + else: + guesses = from_fp( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + + return not guesses diff --git a/.venv/Lib/site-packages/charset_normalizer/cd.py b/.venv/Lib/site-packages/charset_normalizer/cd.py new file mode 100644 index 0000000..71a3ed5 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/cd.py @@ -0,0 +1,395 @@ +from __future__ import annotations + +import importlib +from codecs import IncrementalDecoder +from collections import Counter +from functools import lru_cache +from typing import Counter as TypeCounter + +from .constant import ( + FREQUENCIES, + KO_NAMES, + LANGUAGE_SUPPORTED_COUNT, + TOO_SMALL_SEQUENCE, + ZH_NAMES, +) +from .md import is_suspiciously_successive_range +from .models import CoherenceMatches +from .utils import ( + is_accentuated, + is_latin, + is_multi_byte_encoding, + is_unicode_range_secondary, + unicode_range, +) + + +def encoding_unicode_range(iana_name: str) -> list[str]: + """ + Return associated unicode ranges in a single byte code page. + """ + if is_multi_byte_encoding(iana_name): + raise OSError("Function not supported on multi-byte code page") + + decoder = importlib.import_module(f"encodings.{iana_name}").IncrementalDecoder + + p: IncrementalDecoder = decoder(errors="ignore") + seen_ranges: dict[str, int] = {} + character_count: int = 0 + + for i in range(0x40, 0xFF): + chunk: str = p.decode(bytes([i])) + + if chunk: + character_range: str | None = unicode_range(chunk) + + if character_range is None: + continue + + if is_unicode_range_secondary(character_range) is False: + if character_range not in seen_ranges: + seen_ranges[character_range] = 0 + seen_ranges[character_range] += 1 + character_count += 1 + + return sorted( + [ + character_range + for character_range in seen_ranges + if seen_ranges[character_range] / character_count >= 0.15 + ] + ) + + +def unicode_range_languages(primary_range: str) -> list[str]: + """ + Return inferred languages used with a unicode range. + """ + languages: list[str] = [] + + for language, characters in FREQUENCIES.items(): + for character in characters: + if unicode_range(character) == primary_range: + languages.append(language) + break + + return languages + + +@lru_cache() +def encoding_languages(iana_name: str) -> list[str]: + """ + Single-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + unicode_ranges: list[str] = encoding_unicode_range(iana_name) + primary_range: str | None = None + + for specified_range in unicode_ranges: + if "Latin" not in specified_range: + primary_range = specified_range + break + + if primary_range is None: + return ["Latin Based"] + + return unicode_range_languages(primary_range) + + +@lru_cache() +def mb_encoding_languages(iana_name: str) -> list[str]: + """ + Multi-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + if ( + iana_name.startswith("shift_") + or iana_name.startswith("iso2022_jp") + or iana_name.startswith("euc_j") + or iana_name == "cp932" + ): + return ["Japanese"] + if iana_name.startswith("gb") or iana_name in ZH_NAMES: + return ["Chinese"] + if iana_name.startswith("iso2022_kr") or iana_name in KO_NAMES: + return ["Korean"] + + return [] + + +@lru_cache(maxsize=LANGUAGE_SUPPORTED_COUNT) +def get_target_features(language: str) -> tuple[bool, bool]: + """ + Determine main aspects from a supported language if it contains accents and if is pure Latin. + """ + target_have_accents: bool = False + target_pure_latin: bool = True + + for character in FREQUENCIES[language]: + if not target_have_accents and is_accentuated(character): + target_have_accents = True + if target_pure_latin and is_latin(character) is False: + target_pure_latin = False + + return target_have_accents, target_pure_latin + + +def alphabet_languages( + characters: list[str], ignore_non_latin: bool = False +) -> list[str]: + """ + Return associated languages associated to given characters. + """ + languages: list[tuple[str, float]] = [] + + source_have_accents = any(is_accentuated(character) for character in characters) + + for language, language_characters in FREQUENCIES.items(): + target_have_accents, target_pure_latin = get_target_features(language) + + if ignore_non_latin and target_pure_latin is False: + continue + + if target_have_accents is False and source_have_accents: + continue + + character_count: int = len(language_characters) + + character_match_count: int = len( + [c for c in language_characters if c in characters] + ) + + ratio: float = character_match_count / character_count + + if ratio >= 0.2: + languages.append((language, ratio)) + + languages = sorted(languages, key=lambda x: x[1], reverse=True) + + return [compatible_language[0] for compatible_language in languages] + + +def characters_popularity_compare( + language: str, ordered_characters: list[str] +) -> float: + """ + Determine if a ordered characters list (by occurrence from most appearance to rarest) match a particular language. + The result is a ratio between 0. (absolutely no correspondence) and 1. (near perfect fit). + Beware that is function is not strict on the match in order to ease the detection. (Meaning close match is 1.) + """ + if language not in FREQUENCIES: + raise ValueError(f"{language} not available") + + character_approved_count: int = 0 + FREQUENCIES_language_set = set(FREQUENCIES[language]) + + ordered_characters_count: int = len(ordered_characters) + target_language_characters_count: int = len(FREQUENCIES[language]) + + large_alphabet: bool = target_language_characters_count > 26 + + for character, character_rank in zip( + ordered_characters, range(0, ordered_characters_count) + ): + if character not in FREQUENCIES_language_set: + continue + + character_rank_in_language: int = FREQUENCIES[language].index(character) + expected_projection_ratio: float = ( + target_language_characters_count / ordered_characters_count + ) + character_rank_projection: int = int(character_rank * expected_projection_ratio) + + if ( + large_alphabet is False + and abs(character_rank_projection - character_rank_in_language) > 4 + ): + continue + + if ( + large_alphabet is True + and abs(character_rank_projection - character_rank_in_language) + < target_language_characters_count / 3 + ): + character_approved_count += 1 + continue + + characters_before_source: list[str] = FREQUENCIES[language][ + 0:character_rank_in_language + ] + characters_after_source: list[str] = FREQUENCIES[language][ + character_rank_in_language: + ] + characters_before: list[str] = ordered_characters[0:character_rank] + characters_after: list[str] = ordered_characters[character_rank:] + + before_match_count: int = len( + set(characters_before) & set(characters_before_source) + ) + + after_match_count: int = len( + set(characters_after) & set(characters_after_source) + ) + + if len(characters_before_source) == 0 and before_match_count <= 4: + character_approved_count += 1 + continue + + if len(characters_after_source) == 0 and after_match_count <= 4: + character_approved_count += 1 + continue + + if ( + before_match_count / len(characters_before_source) >= 0.4 + or after_match_count / len(characters_after_source) >= 0.4 + ): + character_approved_count += 1 + continue + + return character_approved_count / len(ordered_characters) + + +def alpha_unicode_split(decoded_sequence: str) -> list[str]: + """ + Given a decoded text sequence, return a list of str. Unicode range / alphabet separation. + Ex. a text containing English/Latin with a bit a Hebrew will return two items in the resulting list; + One containing the latin letters and the other hebrew. + """ + layers: dict[str, str] = {} + + for character in decoded_sequence: + if character.isalpha() is False: + continue + + character_range: str | None = unicode_range(character) + + if character_range is None: + continue + + layer_target_range: str | None = None + + for discovered_range in layers: + if ( + is_suspiciously_successive_range(discovered_range, character_range) + is False + ): + layer_target_range = discovered_range + break + + if layer_target_range is None: + layer_target_range = character_range + + if layer_target_range not in layers: + layers[layer_target_range] = character.lower() + continue + + layers[layer_target_range] += character.lower() + + return list(layers.values()) + + +def merge_coherence_ratios(results: list[CoherenceMatches]) -> CoherenceMatches: + """ + This function merge results previously given by the function coherence_ratio. + The return type is the same as coherence_ratio. + """ + per_language_ratios: dict[str, list[float]] = {} + for result in results: + for sub_result in result: + language, ratio = sub_result + if language not in per_language_ratios: + per_language_ratios[language] = [ratio] + continue + per_language_ratios[language].append(ratio) + + merge = [ + ( + language, + round( + sum(per_language_ratios[language]) / len(per_language_ratios[language]), + 4, + ), + ) + for language in per_language_ratios + ] + + return sorted(merge, key=lambda x: x[1], reverse=True) + + +def filter_alt_coherence_matches(results: CoherenceMatches) -> CoherenceMatches: + """ + We shall NOT return "English—" in CoherenceMatches because it is an alternative + of "English". This function only keeps the best match and remove the em-dash in it. + """ + index_results: dict[str, list[float]] = dict() + + for result in results: + language, ratio = result + no_em_name: str = language.replace("—", "") + + if no_em_name not in index_results: + index_results[no_em_name] = [] + + index_results[no_em_name].append(ratio) + + if any(len(index_results[e]) > 1 for e in index_results): + filtered_results: CoherenceMatches = [] + + for language in index_results: + filtered_results.append((language, max(index_results[language]))) + + return filtered_results + + return results + + +@lru_cache(maxsize=2048) +def coherence_ratio( + decoded_sequence: str, threshold: float = 0.1, lg_inclusion: str | None = None +) -> CoherenceMatches: + """ + Detect ANY language that can be identified in given sequence. The sequence will be analysed by layers. + A layer = Character extraction by alphabets/ranges. + """ + + results: list[tuple[str, float]] = [] + ignore_non_latin: bool = False + + sufficient_match_count: int = 0 + + lg_inclusion_list = lg_inclusion.split(",") if lg_inclusion is not None else [] + if "Latin Based" in lg_inclusion_list: + ignore_non_latin = True + lg_inclusion_list.remove("Latin Based") + + for layer in alpha_unicode_split(decoded_sequence): + sequence_frequencies: TypeCounter[str] = Counter(layer) + most_common = sequence_frequencies.most_common() + + character_count: int = sum(o for c, o in most_common) + + if character_count <= TOO_SMALL_SEQUENCE: + continue + + popular_character_ordered: list[str] = [c for c, o in most_common] + + for language in lg_inclusion_list or alphabet_languages( + popular_character_ordered, ignore_non_latin + ): + ratio: float = characters_popularity_compare( + language, popular_character_ordered + ) + + if ratio < threshold: + continue + elif ratio >= 0.8: + sufficient_match_count += 1 + + results.append((language, round(ratio, 4))) + + if sufficient_match_count >= 3: + break + + return sorted( + filter_alt_coherence_matches(results), key=lambda x: x[1], reverse=True + ) diff --git a/.venv/Lib/site-packages/charset_normalizer/cli/__init__.py b/.venv/Lib/site-packages/charset_normalizer/cli/__init__.py new file mode 100644 index 0000000..543a5a4 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/cli/__init__.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from .__main__ import cli_detect, query_yes_no + +__all__ = ( + "cli_detect", + "query_yes_no", +) diff --git a/.venv/Lib/site-packages/charset_normalizer/cli/__main__.py b/.venv/Lib/site-packages/charset_normalizer/cli/__main__.py new file mode 100644 index 0000000..cb64156 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/cli/__main__.py @@ -0,0 +1,381 @@ +from __future__ import annotations + +import argparse +import sys +import typing +from json import dumps +from os.path import abspath, basename, dirname, join, realpath +from platform import python_version +from unicodedata import unidata_version + +import charset_normalizer.md as md_module +from charset_normalizer import from_fp +from charset_normalizer.models import CliDetectionResult +from charset_normalizer.version import __version__ + + +def query_yes_no(question: str, default: str = "yes") -> bool: + """Ask a yes/no question via input() and return their answer. + + "question" is a string that is presented to the user. + "default" is the presumed answer if the user just hits . + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + The "answer" return value is True for "yes" or False for "no". + + Credit goes to (c) https://stackoverflow.com/questions/3041986/apt-command-line-interface-like-yes-no-input + """ + valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == "": + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no' (or 'y' or 'n').\n") + + +class FileType: + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + - encoding -- The file's encoding. Accepts the same values as the + builtin open() function. + - errors -- A string indicating how encoding and decoding errors are to + be handled. Accepts the same value as the builtin open() function. + + Backported from CPython 3.12 + """ + + def __init__( + self, + mode: str = "r", + bufsize: int = -1, + encoding: str | None = None, + errors: str | None = None, + ): + self._mode = mode + self._bufsize = bufsize + self._encoding = encoding + self._errors = errors + + def __call__(self, string: str) -> typing.IO: # type: ignore[type-arg] + # the special argument "-" means sys.std{in,out} + if string == "-": + if "r" in self._mode: + return sys.stdin.buffer if "b" in self._mode else sys.stdin + elif any(c in self._mode for c in "wax"): + return sys.stdout.buffer if "b" in self._mode else sys.stdout + else: + msg = f'argument "-" with mode {self._mode}' + raise ValueError(msg) + + # all other arguments are used as file names + try: + return open(string, self._mode, self._bufsize, self._encoding, self._errors) + except OSError as e: + message = f"can't open '{string}': {e}" + raise argparse.ArgumentTypeError(message) + + def __repr__(self) -> str: + args = self._mode, self._bufsize + kwargs = [("encoding", self._encoding), ("errors", self._errors)] + args_str = ", ".join( + [repr(arg) for arg in args if arg != -1] + + [f"{kw}={arg!r}" for kw, arg in kwargs if arg is not None] + ) + return f"{type(self).__name__}({args_str})" + + +def cli_detect(argv: list[str] | None = None) -> int: + """ + CLI assistant using ARGV and ArgumentParser + :param argv: + :return: 0 if everything is fine, anything else equal trouble + """ + parser = argparse.ArgumentParser( + description="The Real First Universal Charset Detector. " + "Discover originating encoding used on text file. " + "Normalize text to unicode." + ) + + parser.add_argument( + "files", type=FileType("rb"), nargs="+", help="File(s) to be analysed" + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + default=False, + dest="verbose", + help="Display complementary information about file if any. " + "Stdout will contain logs about the detection process.", + ) + parser.add_argument( + "-a", + "--with-alternative", + action="store_true", + default=False, + dest="alternatives", + help="Output complementary possibilities if any. Top-level JSON WILL be a list.", + ) + parser.add_argument( + "-n", + "--normalize", + action="store_true", + default=False, + dest="normalize", + help="Permit to normalize input file. If not set, program does not write anything.", + ) + parser.add_argument( + "-m", + "--minimal", + action="store_true", + default=False, + dest="minimal", + help="Only output the charset detected to STDOUT. Disabling JSON output.", + ) + parser.add_argument( + "-r", + "--replace", + action="store_true", + default=False, + dest="replace", + help="Replace file when trying to normalize it instead of creating a new one.", + ) + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + dest="force", + help="Replace file without asking if you are sure, use this flag with caution.", + ) + parser.add_argument( + "-i", + "--no-preemptive", + action="store_true", + default=False, + dest="no_preemptive", + help="Disable looking at a charset declaration to hint the detector.", + ) + parser.add_argument( + "-t", + "--threshold", + action="store", + default=0.2, + type=float, + dest="threshold", + help="Define a custom maximum amount of noise allowed in decoded content. 0. <= noise <= 1.", + ) + parser.add_argument( + "--version", + action="version", + version="Charset-Normalizer {} - Python {} - Unicode {} - SpeedUp {}".format( + __version__, + python_version(), + unidata_version, + "OFF" if md_module.__file__.lower().endswith(".py") else "ON", + ), + help="Show version information and exit.", + ) + + args = parser.parse_args(argv) + + if args.replace is True and args.normalize is False: + if args.files: + for my_file in args.files: + my_file.close() + print("Use --replace in addition of --normalize only.", file=sys.stderr) + return 1 + + if args.force is True and args.replace is False: + if args.files: + for my_file in args.files: + my_file.close() + print("Use --force in addition of --replace only.", file=sys.stderr) + return 1 + + if args.threshold < 0.0 or args.threshold > 1.0: + if args.files: + for my_file in args.files: + my_file.close() + print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr) + return 1 + + x_ = [] + + for my_file in args.files: + matches = from_fp( + my_file, + threshold=args.threshold, + explain=args.verbose, + preemptive_behaviour=args.no_preemptive is False, + ) + + best_guess = matches.best() + + if best_guess is None: + print( + 'Unable to identify originating encoding for "{}". {}'.format( + my_file.name, + ( + "Maybe try increasing maximum amount of chaos." + if args.threshold < 1.0 + else "" + ), + ), + file=sys.stderr, + ) + x_.append( + CliDetectionResult( + abspath(my_file.name), + None, + [], + [], + "Unknown", + [], + False, + 1.0, + 0.0, + None, + True, + ) + ) + else: + x_.append( + CliDetectionResult( + abspath(my_file.name), + best_guess.encoding, + best_guess.encoding_aliases, + [ + cp + for cp in best_guess.could_be_from_charset + if cp != best_guess.encoding + ], + best_guess.language, + best_guess.alphabets, + best_guess.bom, + best_guess.percent_chaos, + best_guess.percent_coherence, + None, + True, + ) + ) + + if len(matches) > 1 and args.alternatives: + for el in matches: + if el != best_guess: + x_.append( + CliDetectionResult( + abspath(my_file.name), + el.encoding, + el.encoding_aliases, + [ + cp + for cp in el.could_be_from_charset + if cp != el.encoding + ], + el.language, + el.alphabets, + el.bom, + el.percent_chaos, + el.percent_coherence, + None, + False, + ) + ) + + if args.normalize is True: + if best_guess.encoding.startswith("utf") is True: + print( + '"{}" file does not need to be normalized, as it already came from unicode.'.format( + my_file.name + ), + file=sys.stderr, + ) + if my_file.closed is False: + my_file.close() + continue + + dir_path = dirname(realpath(my_file.name)) + file_name = basename(realpath(my_file.name)) + + o_: list[str] = file_name.split(".") + + if args.replace is False: + o_.insert(-1, best_guess.encoding) + if my_file.closed is False: + my_file.close() + elif ( + args.force is False + and query_yes_no( + 'Are you sure to normalize "{}" by replacing it ?'.format( + my_file.name + ), + "no", + ) + is False + ): + if my_file.closed is False: + my_file.close() + continue + + try: + x_[0].unicode_path = join(dir_path, ".".join(o_)) + + with open(x_[0].unicode_path, "wb") as fp: + fp.write(best_guess.output()) + except OSError as e: + print(str(e), file=sys.stderr) + if my_file.closed is False: + my_file.close() + return 2 + + if my_file.closed is False: + my_file.close() + + if args.minimal is False: + print( + dumps( + [el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__, + ensure_ascii=True, + indent=4, + ) + ) + else: + for my_file in args.files: + print( + ", ".join( + [ + el.encoding or "undefined" + for el in x_ + if el.path == abspath(my_file.name) + ] + ) + ) + + return 0 + + +if __name__ == "__main__": + cli_detect() diff --git a/.venv/Lib/site-packages/charset_normalizer/cli/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/cli/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8045dea34c3646d45a76ab8abd194b6fcc2e69b GIT binary patch literal 360 zcmXw!!Ab)$5QdZ8tu3v+_yk3~^k920B8a6Zh!l#KB{0O@(WcFAYLYE%Z@z&y-$3wL ze1io~9;CNIZ=Re*oI~dS{+W08vZNz6MTO{;+3T=<(g$Y=A5NL z8tNbm<1mZjh&YTJWz=tM>nnX;b!46!FTFCk+w$5As&=9kr06m6)!|ctwuBNL{KfRTw|2dWmToDoWU@rt8=Hj@gEi)Baii{qQyx!OG?I_#L^7Awp{Hy% zlUi-i4k`Q)K;_B<3|3{;I?GI2Xgs%Fic){k9nrB1Ip?43_?r!OQP>Hdo3=ytFMr)= ASO5S3 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/cli/__pycache__/__main__.cpython-312.pyc b/.venv/Lib/site-packages/charset_normalizer/cli/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc8d7bb7613193ce0eabe08e39ea973d648101dc GIT binary patch literal 14410 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 Az5oCK literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/constant.py b/.venv/Lib/site-packages/charset_normalizer/constant.py new file mode 100644 index 0000000..cc71a01 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/constant.py @@ -0,0 +1,2015 @@ +from __future__ import annotations + +from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE +from encodings.aliases import aliases +from re import IGNORECASE +from re import compile as re_compile + +# Contain for each eligible encoding a list of/item bytes SIG/BOM +ENCODING_MARKS: dict[str, bytes | list[bytes]] = { + "utf_8": BOM_UTF8, + "utf_7": [ + b"\x2b\x2f\x76\x38", + b"\x2b\x2f\x76\x39", + b"\x2b\x2f\x76\x2b", + b"\x2b\x2f\x76\x2f", + b"\x2b\x2f\x76\x38\x2d", + ], + "gb18030": b"\x84\x31\x95\x33", + "utf_32": [BOM_UTF32_BE, BOM_UTF32_LE], + "utf_16": [BOM_UTF16_BE, BOM_UTF16_LE], +} + +TOO_SMALL_SEQUENCE: int = 32 +TOO_BIG_SEQUENCE: int = int(10e6) + +UTF8_MAXIMAL_ALLOCATION: int = 1_112_064 + +# Up-to-date Unicode ucd/15.0.0 +UNICODE_RANGES_COMBINED: dict[str, range] = { + "Control character": range(32), + "Basic Latin": range(32, 128), + "Latin-1 Supplement": range(128, 256), + "Latin Extended-A": range(256, 384), + "Latin Extended-B": range(384, 592), + "IPA Extensions": range(592, 688), + "Spacing Modifier Letters": range(688, 768), + "Combining Diacritical Marks": range(768, 880), + "Greek and Coptic": range(880, 1024), + "Cyrillic": range(1024, 1280), + "Cyrillic Supplement": range(1280, 1328), + "Armenian": range(1328, 1424), + "Hebrew": range(1424, 1536), + "Arabic": range(1536, 1792), + "Syriac": range(1792, 1872), + "Arabic Supplement": range(1872, 1920), + "Thaana": range(1920, 1984), + "NKo": range(1984, 2048), + "Samaritan": range(2048, 2112), + "Mandaic": range(2112, 2144), + "Syriac Supplement": range(2144, 2160), + "Arabic Extended-B": range(2160, 2208), + "Arabic Extended-A": range(2208, 2304), + "Devanagari": range(2304, 2432), + "Bengali": range(2432, 2560), + "Gurmukhi": range(2560, 2688), + "Gujarati": range(2688, 2816), + "Oriya": range(2816, 2944), + "Tamil": range(2944, 3072), + "Telugu": range(3072, 3200), + "Kannada": range(3200, 3328), + "Malayalam": range(3328, 3456), + "Sinhala": range(3456, 3584), + "Thai": range(3584, 3712), + "Lao": range(3712, 3840), + "Tibetan": range(3840, 4096), + "Myanmar": range(4096, 4256), + "Georgian": range(4256, 4352), + "Hangul Jamo": range(4352, 4608), + "Ethiopic": range(4608, 4992), + "Ethiopic Supplement": range(4992, 5024), + "Cherokee": range(5024, 5120), + "Unified Canadian Aboriginal Syllabics": range(5120, 5760), + "Ogham": range(5760, 5792), + "Runic": range(5792, 5888), + "Tagalog": range(5888, 5920), + "Hanunoo": range(5920, 5952), + "Buhid": range(5952, 5984), + "Tagbanwa": range(5984, 6016), + "Khmer": range(6016, 6144), + "Mongolian": range(6144, 6320), + "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6400), + "Limbu": range(6400, 6480), + "Tai Le": range(6480, 6528), + "New Tai Lue": range(6528, 6624), + "Khmer Symbols": range(6624, 6656), + "Buginese": range(6656, 6688), + "Tai Tham": range(6688, 6832), + "Combining Diacritical Marks Extended": range(6832, 6912), + "Balinese": range(6912, 7040), + "Sundanese": range(7040, 7104), + "Batak": range(7104, 7168), + "Lepcha": range(7168, 7248), + "Ol Chiki": range(7248, 7296), + "Cyrillic Extended-C": range(7296, 7312), + "Georgian Extended": range(7312, 7360), + "Sundanese Supplement": range(7360, 7376), + "Vedic Extensions": range(7376, 7424), + "Phonetic Extensions": range(7424, 7552), + "Phonetic Extensions Supplement": range(7552, 7616), + "Combining Diacritical Marks Supplement": range(7616, 7680), + "Latin Extended Additional": range(7680, 7936), + "Greek Extended": range(7936, 8192), + "General Punctuation": range(8192, 8304), + "Superscripts and Subscripts": range(8304, 8352), + "Currency Symbols": range(8352, 8400), + "Combining Diacritical Marks for Symbols": range(8400, 8448), + "Letterlike Symbols": range(8448, 8528), + "Number Forms": range(8528, 8592), + "Arrows": range(8592, 8704), + "Mathematical Operators": range(8704, 8960), + "Miscellaneous Technical": range(8960, 9216), + "Control Pictures": range(9216, 9280), + "Optical Character Recognition": range(9280, 9312), + "Enclosed Alphanumerics": range(9312, 9472), + "Box Drawing": range(9472, 9600), + "Block Elements": range(9600, 9632), + "Geometric Shapes": range(9632, 9728), + "Miscellaneous Symbols": range(9728, 9984), + "Dingbats": range(9984, 10176), + "Miscellaneous Mathematical Symbols-A": range(10176, 10224), + "Supplemental Arrows-A": range(10224, 10240), + "Braille Patterns": range(10240, 10496), + "Supplemental Arrows-B": range(10496, 10624), + "Miscellaneous Mathematical Symbols-B": range(10624, 10752), + "Supplemental Mathematical Operators": range(10752, 11008), + "Miscellaneous Symbols and Arrows": range(11008, 11264), + "Glagolitic": range(11264, 11360), + "Latin Extended-C": range(11360, 11392), + "Coptic": range(11392, 11520), + "Georgian Supplement": range(11520, 11568), + "Tifinagh": range(11568, 11648), + "Ethiopic Extended": range(11648, 11744), + "Cyrillic Extended-A": range(11744, 11776), + "Supplemental Punctuation": range(11776, 11904), + "CJK Radicals Supplement": range(11904, 12032), + "Kangxi Radicals": range(12032, 12256), + "Ideographic Description Characters": range(12272, 12288), + "CJK Symbols and Punctuation": range(12288, 12352), + "Hiragana": range(12352, 12448), + "Katakana": range(12448, 12544), + "Bopomofo": range(12544, 12592), + "Hangul Compatibility Jamo": range(12592, 12688), + "Kanbun": range(12688, 12704), + "Bopomofo Extended": range(12704, 12736), + "CJK Strokes": range(12736, 12784), + "Katakana Phonetic Extensions": range(12784, 12800), + "Enclosed CJK Letters and Months": range(12800, 13056), + "CJK Compatibility": range(13056, 13312), + "CJK Unified Ideographs Extension A": range(13312, 19904), + "Yijing Hexagram Symbols": range(19904, 19968), + "CJK Unified Ideographs": range(19968, 40960), + "Yi Syllables": range(40960, 42128), + "Yi Radicals": range(42128, 42192), + "Lisu": range(42192, 42240), + "Vai": range(42240, 42560), + "Cyrillic Extended-B": range(42560, 42656), + "Bamum": range(42656, 42752), + "Modifier Tone Letters": range(42752, 42784), + "Latin Extended-D": range(42784, 43008), + "Syloti Nagri": range(43008, 43056), + "Common Indic Number Forms": range(43056, 43072), + "Phags-pa": range(43072, 43136), + "Saurashtra": range(43136, 43232), + "Devanagari Extended": range(43232, 43264), + "Kayah Li": range(43264, 43312), + "Rejang": range(43312, 43360), + "Hangul Jamo Extended-A": range(43360, 43392), + "Javanese": range(43392, 43488), + "Myanmar Extended-B": range(43488, 43520), + "Cham": range(43520, 43616), + "Myanmar Extended-A": range(43616, 43648), + "Tai Viet": range(43648, 43744), + "Meetei Mayek Extensions": range(43744, 43776), + "Ethiopic Extended-A": range(43776, 43824), + "Latin Extended-E": range(43824, 43888), + "Cherokee Supplement": range(43888, 43968), + "Meetei Mayek": range(43968, 44032), + "Hangul Syllables": range(44032, 55216), + "Hangul Jamo Extended-B": range(55216, 55296), + "High Surrogates": range(55296, 56192), + "High Private Use Surrogates": range(56192, 56320), + "Low Surrogates": range(56320, 57344), + "Private Use Area": range(57344, 63744), + "CJK Compatibility Ideographs": range(63744, 64256), + "Alphabetic Presentation Forms": range(64256, 64336), + "Arabic Presentation Forms-A": range(64336, 65024), + "Variation Selectors": range(65024, 65040), + "Vertical Forms": range(65040, 65056), + "Combining Half Marks": range(65056, 65072), + "CJK Compatibility Forms": range(65072, 65104), + "Small Form Variants": range(65104, 65136), + "Arabic Presentation Forms-B": range(65136, 65280), + "Halfwidth and Fullwidth Forms": range(65280, 65520), + "Specials": range(65520, 65536), + "Linear B Syllabary": range(65536, 65664), + "Linear B Ideograms": range(65664, 65792), + "Aegean Numbers": range(65792, 65856), + "Ancient Greek Numbers": range(65856, 65936), + "Ancient Symbols": range(65936, 66000), + "Phaistos Disc": range(66000, 66048), + "Lycian": range(66176, 66208), + "Carian": range(66208, 66272), + "Coptic Epact Numbers": range(66272, 66304), + "Old Italic": range(66304, 66352), + "Gothic": range(66352, 66384), + "Old Permic": range(66384, 66432), + "Ugaritic": range(66432, 66464), + "Old Persian": range(66464, 66528), + "Deseret": range(66560, 66640), + "Shavian": range(66640, 66688), + "Osmanya": range(66688, 66736), + "Osage": range(66736, 66816), + "Elbasan": range(66816, 66864), + "Caucasian Albanian": range(66864, 66928), + "Vithkuqi": range(66928, 67008), + "Linear A": range(67072, 67456), + "Latin Extended-F": range(67456, 67520), + "Cypriot Syllabary": range(67584, 67648), + "Imperial Aramaic": range(67648, 67680), + "Palmyrene": range(67680, 67712), + "Nabataean": range(67712, 67760), + "Hatran": range(67808, 67840), + "Phoenician": range(67840, 67872), + "Lydian": range(67872, 67904), + "Meroitic Hieroglyphs": range(67968, 68000), + "Meroitic Cursive": range(68000, 68096), + "Kharoshthi": range(68096, 68192), + "Old South Arabian": range(68192, 68224), + "Old North Arabian": range(68224, 68256), + "Manichaean": range(68288, 68352), + "Avestan": range(68352, 68416), + "Inscriptional Parthian": range(68416, 68448), + "Inscriptional Pahlavi": range(68448, 68480), + "Psalter Pahlavi": range(68480, 68528), + "Old Turkic": range(68608, 68688), + "Old Hungarian": range(68736, 68864), + "Hanifi Rohingya": range(68864, 68928), + "Rumi Numeral Symbols": range(69216, 69248), + "Yezidi": range(69248, 69312), + "Arabic Extended-C": range(69312, 69376), + "Old Sogdian": range(69376, 69424), + "Sogdian": range(69424, 69488), + "Old Uyghur": range(69488, 69552), + "Chorasmian": range(69552, 69600), + "Elymaic": range(69600, 69632), + "Brahmi": range(69632, 69760), + "Kaithi": range(69760, 69840), + "Sora Sompeng": range(69840, 69888), + "Chakma": range(69888, 69968), + "Mahajani": range(69968, 70016), + "Sharada": range(70016, 70112), + "Sinhala Archaic Numbers": range(70112, 70144), + "Khojki": range(70144, 70224), + "Multani": range(70272, 70320), + "Khudawadi": range(70320, 70400), + "Grantha": range(70400, 70528), + "Newa": range(70656, 70784), + "Tirhuta": range(70784, 70880), + "Siddham": range(71040, 71168), + "Modi": range(71168, 71264), + "Mongolian Supplement": range(71264, 71296), + "Takri": range(71296, 71376), + "Ahom": range(71424, 71504), + "Dogra": range(71680, 71760), + "Warang Citi": range(71840, 71936), + "Dives Akuru": range(71936, 72032), + "Nandinagari": range(72096, 72192), + "Zanabazar Square": range(72192, 72272), + "Soyombo": range(72272, 72368), + "Unified Canadian Aboriginal Syllabics Extended-A": range(72368, 72384), + "Pau Cin Hau": range(72384, 72448), + "Devanagari Extended-A": range(72448, 72544), + "Bhaiksuki": range(72704, 72816), + "Marchen": range(72816, 72896), + "Masaram Gondi": range(72960, 73056), + "Gunjala Gondi": range(73056, 73136), + "Makasar": range(73440, 73472), + "Kawi": range(73472, 73568), + "Lisu Supplement": range(73648, 73664), + "Tamil Supplement": range(73664, 73728), + "Cuneiform": range(73728, 74752), + "Cuneiform Numbers and Punctuation": range(74752, 74880), + "Early Dynastic Cuneiform": range(74880, 75088), + "Cypro-Minoan": range(77712, 77824), + "Egyptian Hieroglyphs": range(77824, 78896), + "Egyptian Hieroglyph Format Controls": range(78896, 78944), + "Anatolian Hieroglyphs": range(82944, 83584), + "Bamum Supplement": range(92160, 92736), + "Mro": range(92736, 92784), + "Tangsa": range(92784, 92880), + "Bassa Vah": range(92880, 92928), + "Pahawh Hmong": range(92928, 93072), + "Medefaidrin": range(93760, 93856), + "Miao": range(93952, 94112), + "Ideographic Symbols and Punctuation": range(94176, 94208), + "Tangut": range(94208, 100352), + "Tangut Components": range(100352, 101120), + "Khitan Small Script": range(101120, 101632), + "Tangut Supplement": range(101632, 101760), + "Kana Extended-B": range(110576, 110592), + "Kana Supplement": range(110592, 110848), + "Kana Extended-A": range(110848, 110896), + "Small Kana Extension": range(110896, 110960), + "Nushu": range(110960, 111360), + "Duployan": range(113664, 113824), + "Shorthand Format Controls": range(113824, 113840), + "Znamenny Musical Notation": range(118528, 118736), + "Byzantine Musical Symbols": range(118784, 119040), + "Musical Symbols": range(119040, 119296), + "Ancient Greek Musical Notation": range(119296, 119376), + "Kaktovik Numerals": range(119488, 119520), + "Mayan Numerals": range(119520, 119552), + "Tai Xuan Jing Symbols": range(119552, 119648), + "Counting Rod Numerals": range(119648, 119680), + "Mathematical Alphanumeric Symbols": range(119808, 120832), + "Sutton SignWriting": range(120832, 121520), + "Latin Extended-G": range(122624, 122880), + "Glagolitic Supplement": range(122880, 122928), + "Cyrillic Extended-D": range(122928, 123024), + "Nyiakeng Puachue Hmong": range(123136, 123216), + "Toto": range(123536, 123584), + "Wancho": range(123584, 123648), + "Nag Mundari": range(124112, 124160), + "Ethiopic Extended-B": range(124896, 124928), + "Mende Kikakui": range(124928, 125152), + "Adlam": range(125184, 125280), + "Indic Siyaq Numbers": range(126064, 126144), + "Ottoman Siyaq Numbers": range(126208, 126288), + "Arabic Mathematical Alphabetic Symbols": range(126464, 126720), + "Mahjong Tiles": range(126976, 127024), + "Domino Tiles": range(127024, 127136), + "Playing Cards": range(127136, 127232), + "Enclosed Alphanumeric Supplement": range(127232, 127488), + "Enclosed Ideographic Supplement": range(127488, 127744), + "Miscellaneous Symbols and Pictographs": range(127744, 128512), + "Emoticons range(Emoji)": range(128512, 128592), + "Ornamental Dingbats": range(128592, 128640), + "Transport and Map Symbols": range(128640, 128768), + "Alchemical Symbols": range(128768, 128896), + "Geometric Shapes Extended": range(128896, 129024), + "Supplemental Arrows-C": range(129024, 129280), + "Supplemental Symbols and Pictographs": range(129280, 129536), + "Chess Symbols": range(129536, 129648), + "Symbols and Pictographs Extended-A": range(129648, 129792), + "Symbols for Legacy Computing": range(129792, 130048), + "CJK Unified Ideographs Extension B": range(131072, 173792), + "CJK Unified Ideographs Extension C": range(173824, 177984), + "CJK Unified Ideographs Extension D": range(177984, 178208), + "CJK Unified Ideographs Extension E": range(178208, 183984), + "CJK Unified Ideographs Extension F": range(183984, 191472), + "CJK Compatibility Ideographs Supplement": range(194560, 195104), + "CJK Unified Ideographs Extension G": range(196608, 201552), + "CJK Unified Ideographs Extension H": range(201552, 205744), + "Tags": range(917504, 917632), + "Variation Selectors Supplement": range(917760, 918000), + "Supplementary Private Use Area-A": range(983040, 1048576), + "Supplementary Private Use Area-B": range(1048576, 1114112), +} + + +UNICODE_SECONDARY_RANGE_KEYWORD: list[str] = [ + "Supplement", + "Extended", + "Extensions", + "Modifier", + "Marks", + "Punctuation", + "Symbols", + "Forms", + "Operators", + "Miscellaneous", + "Drawing", + "Block", + "Shapes", + "Supplemental", + "Tags", +] + +RE_POSSIBLE_ENCODING_INDICATION = re_compile( + r"(?:(?:encoding)|(?:charset)|(?:coding))(?:[\:= ]{1,10})(?:[\"\']?)([a-zA-Z0-9\-_]+)(?:[\"\']?)", + IGNORECASE, +) + +IANA_NO_ALIASES = [ + "cp720", + "cp737", + "cp856", + "cp874", + "cp875", + "cp1006", + "koi8_r", + "koi8_t", + "koi8_u", +] + +IANA_SUPPORTED: list[str] = sorted( + filter( + lambda x: x.endswith("_codec") is False + and x not in {"rot_13", "tactis", "mbcs"}, + list(set(aliases.values())) + IANA_NO_ALIASES, + ) +) + +IANA_SUPPORTED_COUNT: int = len(IANA_SUPPORTED) + +# pre-computed code page that are similar using the function cp_similarity. +IANA_SUPPORTED_SIMILAR: dict[str, list[str]] = { + "cp037": ["cp1026", "cp1140", "cp273", "cp500"], + "cp1026": ["cp037", "cp1140", "cp273", "cp500"], + "cp1125": ["cp866"], + "cp1140": ["cp037", "cp1026", "cp273", "cp500"], + "cp1250": ["iso8859_2"], + "cp1251": ["kz1048", "ptcp154"], + "cp1252": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1253": ["iso8859_7"], + "cp1254": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1257": ["iso8859_13"], + "cp273": ["cp037", "cp1026", "cp1140", "cp500"], + "cp437": ["cp850", "cp858", "cp860", "cp861", "cp862", "cp863", "cp865"], + "cp500": ["cp037", "cp1026", "cp1140", "cp273"], + "cp850": ["cp437", "cp857", "cp858", "cp865"], + "cp857": ["cp850", "cp858", "cp865"], + "cp858": ["cp437", "cp850", "cp857", "cp865"], + "cp860": ["cp437", "cp861", "cp862", "cp863", "cp865"], + "cp861": ["cp437", "cp860", "cp862", "cp863", "cp865"], + "cp862": ["cp437", "cp860", "cp861", "cp863", "cp865"], + "cp863": ["cp437", "cp860", "cp861", "cp862", "cp865"], + "cp865": ["cp437", "cp850", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863"], + "cp866": ["cp1125"], + "iso8859_10": ["iso8859_14", "iso8859_15", "iso8859_4", "iso8859_9", "latin_1"], + "iso8859_11": ["tis_620"], + "iso8859_13": ["cp1257"], + "iso8859_14": [ + "iso8859_10", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_15": [ + "cp1252", + "cp1254", + "iso8859_10", + "iso8859_14", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_16": [ + "iso8859_14", + "iso8859_15", + "iso8859_2", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_2": ["cp1250", "iso8859_16", "iso8859_4"], + "iso8859_3": ["iso8859_14", "iso8859_15", "iso8859_16", "iso8859_9", "latin_1"], + "iso8859_4": ["iso8859_10", "iso8859_2", "iso8859_9", "latin_1"], + "iso8859_7": ["cp1253"], + "iso8859_9": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "latin_1", + ], + "kz1048": ["cp1251", "ptcp154"], + "latin_1": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "iso8859_9", + ], + "mac_iceland": ["mac_roman", "mac_turkish"], + "mac_roman": ["mac_iceland", "mac_turkish"], + "mac_turkish": ["mac_iceland", "mac_roman"], + "ptcp154": ["cp1251", "kz1048"], + "tis_620": ["iso8859_11"], +} + + +CHARDET_CORRESPONDENCE: dict[str, str] = { + "iso2022_kr": "ISO-2022-KR", + "iso2022_jp": "ISO-2022-JP", + "euc_kr": "EUC-KR", + "tis_620": "TIS-620", + "utf_32": "UTF-32", + "euc_jp": "EUC-JP", + "koi8_r": "KOI8-R", + "iso8859_1": "ISO-8859-1", + "iso8859_2": "ISO-8859-2", + "iso8859_5": "ISO-8859-5", + "iso8859_6": "ISO-8859-6", + "iso8859_7": "ISO-8859-7", + "iso8859_8": "ISO-8859-8", + "utf_16": "UTF-16", + "cp855": "IBM855", + "mac_cyrillic": "MacCyrillic", + "gb2312": "GB2312", + "gb18030": "GB18030", + "cp932": "CP932", + "cp866": "IBM866", + "utf_8": "utf-8", + "utf_8_sig": "UTF-8-SIG", + "shift_jis": "SHIFT_JIS", + "big5": "Big5", + "cp1250": "windows-1250", + "cp1251": "windows-1251", + "cp1252": "Windows-1252", + "cp1253": "windows-1253", + "cp1255": "windows-1255", + "cp1256": "windows-1256", + "cp1254": "Windows-1254", + "cp949": "CP949", +} + + +COMMON_SAFE_ASCII_CHARACTERS: set[str] = { + "<", + ">", + "=", + ":", + "/", + "&", + ";", + "{", + "}", + "[", + "]", + ",", + "|", + '"', + "-", + "(", + ")", +} + +# Sample character sets — replace with full lists if needed +COMMON_CHINESE_CHARACTERS = "的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞" + +COMMON_JAPANESE_CHARACTERS = "日一国年大十二本中長出三時行見月分後前生五間上東四今金九入学高円子外八六下来気小七山話女北午百書先名川千水半男西電校語土木聞食車何南万毎白天母火右読友左休父雨" + +COMMON_KOREAN_CHARACTERS = "一二三四五六七八九十百千萬上下左右中人女子大小山川日月火水木金土父母天地國名年時文校學生" + +# Combine all into a set +COMMON_CJK_CHARACTERS = set( + "".join( + [ + COMMON_CHINESE_CHARACTERS, + COMMON_JAPANESE_CHARACTERS, + COMMON_KOREAN_CHARACTERS, + ] + ) +) + +KO_NAMES: set[str] = {"johab", "cp949", "euc_kr"} +ZH_NAMES: set[str] = {"big5", "cp950", "big5hkscs", "hz"} + +# Logging LEVEL below DEBUG +TRACE: int = 5 + + +# Language label that contain the em dash "—" +# character are to be considered alternative seq to origin +FREQUENCIES: dict[str, list[str]] = { + "English": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "u", + "m", + "f", + "p", + "g", + "w", + "y", + "b", + "v", + "k", + "x", + "j", + "z", + "q", + ], + "English—": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "m", + "u", + "f", + "p", + "g", + "w", + "b", + "y", + "v", + "k", + "j", + "x", + "z", + "q", + ], + "German": [ + "e", + "n", + "i", + "r", + "s", + "t", + "a", + "d", + "h", + "u", + "l", + "g", + "o", + "c", + "m", + "b", + "f", + "k", + "w", + "z", + "p", + "v", + "ü", + "ä", + "ö", + "j", + ], + "French": [ + "e", + "a", + "s", + "n", + "i", + "t", + "r", + "l", + "u", + "o", + "d", + "c", + "p", + "m", + "é", + "v", + "g", + "f", + "b", + "h", + "q", + "à", + "x", + "è", + "y", + "j", + ], + "Dutch": [ + "e", + "n", + "a", + "i", + "r", + "t", + "o", + "d", + "s", + "l", + "g", + "h", + "v", + "m", + "u", + "k", + "c", + "p", + "b", + "w", + "j", + "z", + "f", + "y", + "x", + "ë", + ], + "Italian": [ + "e", + "i", + "a", + "o", + "n", + "l", + "t", + "r", + "s", + "c", + "d", + "u", + "p", + "m", + "g", + "v", + "f", + "b", + "z", + "h", + "q", + "è", + "à", + "k", + "y", + "ò", + ], + "Polish": [ + "a", + "i", + "o", + "e", + "n", + "r", + "z", + "w", + "s", + "c", + "t", + "k", + "y", + "d", + "p", + "m", + "u", + "l", + "j", + "ł", + "g", + "b", + "h", + "ą", + "ę", + "ó", + ], + "Spanish": [ + "e", + "a", + "o", + "n", + "s", + "r", + "i", + "l", + "d", + "t", + "c", + "u", + "m", + "p", + "b", + "g", + "v", + "f", + "y", + "ó", + "h", + "q", + "í", + "j", + "z", + "á", + ], + "Russian": [ + "о", + "а", + "е", + "и", + "н", + "с", + "т", + "р", + "в", + "л", + "к", + "м", + "д", + "п", + "у", + "г", + "я", + "ы", + "з", + "б", + "й", + "ь", + "ч", + "х", + "ж", + "ц", + ], + # Jap-Kanji + "Japanese": [ + "人", + "一", + "大", + "亅", + "丁", + "丨", + "竹", + "笑", + "口", + "日", + "今", + "二", + "å½³", + "行", + "十", + "土", + "丶", + "寸", + "寺", + "時", + "乙", + "丿", + "乂", + "气", + "気", + "冂", + "å·¾", + "亠", + "市", + "目", + "儿", + "見", + "八", + "小", + "凵", + "県", + "月", + "彐", + "門", + "間", + "木", + "東", + "å±±", + "出", + "本", + "中", + "刀", + "分", + "耳", + "又", + "取", + "最", + "言", + "田", + "心", + "思", + "刂", + "前", + "京", + "å°¹", + "事", + "生", + "厶", + "云", + "会", + "未", + "来", + "白", + "冫", + "楽", + "灬", + "馬", + "å°¸", + "å°º", + "駅", + "明", + "耂", + "者", + "了", + "阝", + "都", + "高", + "卜", + "占", + "厂", + "广", + "店", + "子", + "申", + "奄", + "亻", + "俺", + "上", + "方", + "冖", + "å­¦", + "è¡£", + "艮", + "食", + "自", + ], + # Jap-Katakana + "Japanese—": [ + "ー", + "ン", + "ス", + "・", + "ル", + "ト", + "リ", + "イ", + "ア", + "ラ", + "ッ", + "ク", + "ド", + "シ", + "レ", + "ジ", + "タ", + "フ", + "ロ", + "カ", + "テ", + "マ", + "ィ", + "グ", + "バ", + "ム", + "プ", + "オ", + "コ", + "デ", + "ニ", + "ウ", + "メ", + "サ", + "ビ", + "ナ", + "ブ", + "ャ", + "エ", + "ュ", + "チ", + "キ", + "ズ", + "ダ", + "パ", + "ミ", + "ェ", + "ョ", + "ハ", + "セ", + "ベ", + "ガ", + "モ", + "ツ", + "ネ", + "ボ", + "ソ", + "ノ", + "ァ", + "ヴ", + "ワ", + "ポ", + "ペ", + "ピ", + "ケ", + "ゴ", + "ギ", + "ザ", + "ホ", + "ゲ", + "ォ", + "ヤ", + "ヒ", + "ユ", + "ヨ", + "ヘ", + "ゼ", + "ヌ", + "ゥ", + "ゾ", + "ヶ", + "ヂ", + "ヲ", + "ヅ", + "ヵ", + "ヱ", + "ヰ", + "ヮ", + "ヽ", + "゠", + "ヾ", + "ヷ", + "ヿ", + "ヸ", + "ヹ", + "ヺ", + ], + # Jap-Hiragana + "Japanese——": [ + "の", + "に", + "る", + "た", + "と", + "は", + "し", + "い", + "を", + "で", + "て", + "が", + "な", + "れ", + "か", + "ら", + "さ", + "っ", + "り", + "す", + "あ", + "も", + "こ", + "ま", + "う", + "く", + "よ", + "き", + "ん", + "め", + "お", + "け", + "そ", + "つ", + "だ", + "や", + "え", + "ど", + "わ", + "ち", + "み", + "せ", + "じ", + "ば", + "へ", + "び", + "ず", + "ろ", + "ほ", + "げ", + "む", + "べ", + "ひ", + "ょ", + "ゆ", + "ぶ", + "ご", + "ゃ", + "ね", + "ふ", + "ぐ", + "ぎ", + "ぼ", + "ゅ", + "づ", + "ざ", + "ぞ", + "ぬ", + "ぜ", + "ぱ", + "ぽ", + "ぷ", + "ぴ", + "ぃ", + "ぁ", + "ぇ", + "ぺ", + "ゞ", + "ぢ", + "ぉ", + "ぅ", + "ゐ", + "ゝ", + "ゑ", + "゛", + "゜", + "ゎ", + "ゔ", + "゚", + "ゟ", + "゙", + "ゕ", + "ゖ", + ], + "Portuguese": [ + "a", + "e", + "o", + "s", + "i", + "r", + "d", + "n", + "t", + "m", + "u", + "c", + "l", + "p", + "g", + "v", + "b", + "f", + "h", + "ã", + "q", + "é", + "ç", + "á", + "z", + "í", + ], + "Swedish": [ + "e", + "a", + "n", + "r", + "t", + "s", + "i", + "l", + "d", + "o", + "m", + "k", + "g", + "v", + "h", + "f", + "u", + "p", + "ä", + "c", + "b", + "ö", + "Ã¥", + "y", + "j", + "x", + ], + "Chinese": [ + "的", + "一", + "是", + "不", + "了", + "在", + "人", + "有", + "我", + "他", + "这", + "个", + "们", + "中", + "来", + "上", + "大", + "为", + "和", + "国", + "地", + "到", + "以", + "说", + "时", + "要", + "å°±", + "出", + "会", + "可", + "也", + "ä½ ", + "对", + "生", + "能", + "而", + "子", + "那", + "得", + "于", + "着", + "下", + "自", + "之", + "å¹´", + "过", + "发", + "后", + "作", + "里", + "用", + "道", + "行", + "所", + "然", + "å®¶", + "种", + "事", + "成", + "方", + "多", + "经", + "么", + "去", + "法", + "å­¦", + "如", + "都", + "同", + "现", + "当", + "没", + "动", + "面", + "èµ·", + "看", + "定", + "天", + "分", + "还", + "进", + "好", + "小", + "部", + "其", + "些", + "主", + "æ ·", + "理", + "心", + "她", + "本", + "前", + "开", + "但", + "因", + "只", + "从", + "想", + "实", + ], + "Ukrainian": [ + "о", + "а", + "н", + "і", + "и", + "р", + "в", + "т", + "е", + "с", + "к", + "л", + "у", + "д", + "м", + "п", + "з", + "я", + "ь", + "б", + "г", + "й", + "ч", + "х", + "ц", + "ї", + ], + "Norwegian": [ + "e", + "r", + "n", + "t", + "a", + "s", + "i", + "o", + "l", + "d", + "g", + "k", + "m", + "v", + "f", + "p", + "u", + "b", + "h", + "Ã¥", + "y", + "j", + "ø", + "c", + "æ", + "w", + ], + "Finnish": [ + "a", + "i", + "n", + "t", + "e", + "s", + "l", + "o", + "u", + "k", + "ä", + "m", + "r", + "v", + "j", + "h", + "p", + "y", + "d", + "ö", + "g", + "c", + "b", + "f", + "w", + "z", + ], + "Vietnamese": [ + "n", + "h", + "t", + "i", + "c", + "g", + "a", + "o", + "u", + "m", + "l", + "r", + "à", + "đ", + "s", + "e", + "v", + "p", + "b", + "y", + "ư", + "d", + "á", + "k", + "ộ", + "ế", + ], + "Czech": [ + "o", + "e", + "a", + "n", + "t", + "s", + "i", + "l", + "v", + "r", + "k", + "d", + "u", + "m", + "p", + "í", + "c", + "h", + "z", + "á", + "y", + "j", + "b", + "ě", + "é", + "ř", + ], + "Hungarian": [ + "e", + "a", + "t", + "l", + "s", + "n", + "k", + "r", + "i", + "o", + "z", + "á", + "é", + "g", + "m", + "b", + "y", + "v", + "d", + "h", + "u", + "p", + "j", + "ö", + "f", + "c", + ], + "Korean": [ + "이", + "다", + "에", + "의", + "는", + "로", + "하", + "을", + "가", + "ê³ ", + "지", + "서", + "한", + "은", + "기", + "으", + "년", + "대", + "사", + "시", + "를", + "리", + "도", + "인", + "스", + "일", + ], + "Indonesian": [ + "a", + "n", + "e", + "i", + "r", + "t", + "u", + "s", + "d", + "k", + "m", + "l", + "g", + "p", + "b", + "o", + "h", + "y", + "j", + "c", + "w", + "f", + "v", + "z", + "x", + "q", + ], + "Turkish": [ + "a", + "e", + "i", + "n", + "r", + "l", + "ı", + "k", + "d", + "t", + "s", + "m", + "y", + "u", + "o", + "b", + "ü", + "ş", + "v", + "g", + "z", + "h", + "c", + "p", + "ç", + "ğ", + ], + "Romanian": [ + "e", + "i", + "a", + "r", + "n", + "t", + "u", + "l", + "o", + "c", + "s", + "d", + "p", + "m", + "ă", + "f", + "v", + "î", + "g", + "b", + "ș", + "ț", + "z", + "h", + "â", + "j", + ], + "Farsi": [ + "ا", + "ی", + "ر", + "د", + "ن", + "ه", + "و", + "م", + "ت", + "ب", + "س", + "ل", + "Ú©", + "Ø´", + "ز", + "ف", + "Ú¯", + "ع", + "Ø®", + "ق", + "ج", + "Ø¢", + "Ù¾", + "Ø­", + "Ø·", + "ص", + ], + "Arabic": [ + "ا", + "ل", + "ي", + "م", + "و", + "ن", + "ر", + "ت", + "ب", + "Ø©", + "ع", + "د", + "س", + "ف", + "ه", + "ك", + "ق", + "Ø£", + "Ø­", + "ج", + "Ø´", + "Ø·", + "ص", + "ى", + "Ø®", + "Ø¥", + ], + "Danish": [ + "e", + "r", + "n", + "t", + "a", + "i", + "s", + "d", + "l", + "o", + "g", + "m", + "k", + "f", + "v", + "u", + "b", + "h", + "p", + "Ã¥", + "y", + "ø", + "æ", + "c", + "j", + "w", + ], + "Serbian": [ + "а", + "и", + "о", + "е", + "н", + "р", + "с", + "у", + "т", + "к", + "ј", + "в", + "д", + "м", + "п", + "л", + "г", + "з", + "б", + "a", + "i", + "e", + "o", + "n", + "ц", + "ш", + ], + "Lithuanian": [ + "i", + "a", + "s", + "o", + "r", + "e", + "t", + "n", + "u", + "k", + "m", + "l", + "p", + "v", + "d", + "j", + "g", + "ė", + "b", + "y", + "ų", + "Å¡", + "ž", + "c", + "ą", + "į", + ], + "Slovene": [ + "e", + "a", + "i", + "o", + "n", + "r", + "s", + "l", + "t", + "j", + "v", + "k", + "d", + "p", + "m", + "u", + "z", + "b", + "g", + "h", + "č", + "c", + "Å¡", + "ž", + "f", + "y", + ], + "Slovak": [ + "o", + "a", + "e", + "n", + "i", + "r", + "v", + "t", + "s", + "l", + "k", + "d", + "m", + "p", + "u", + "c", + "h", + "j", + "b", + "z", + "á", + "y", + "ý", + "í", + "č", + "é", + ], + "Hebrew": [ + "י", + "ו", + "ה", + "ל", + "ר", + "ב", + "ת", + "מ", + "א", + "ש", + "× ", + "×¢", + "ם", + "ד", + "×§", + "ח", + "פ", + "ס", + "כ", + "ג", + "ט", + "צ", + "ן", + "ז", + "ך", + ], + "Bulgarian": [ + "а", + "и", + "о", + "е", + "н", + "т", + "р", + "с", + "в", + "л", + "к", + "д", + "п", + "м", + "з", + "г", + "я", + "ъ", + "у", + "б", + "ч", + "ц", + "й", + "ж", + "щ", + "х", + ], + "Croatian": [ + "a", + "i", + "o", + "e", + "n", + "r", + "j", + "s", + "t", + "u", + "k", + "l", + "v", + "d", + "m", + "p", + "g", + "z", + "b", + "c", + "č", + "h", + "Å¡", + "ž", + "ć", + "f", + ], + "Hindi": [ + "क", + "र", + "स", + "न", + "त", + "म", + "ह", + "प", + "य", + "ल", + "व", + "ज", + "द", + "ग", + "ब", + "श", + "ट", + "अ", + "ए", + "थ", + "भ", + "ड", + "च", + "ध", + "ष", + "इ", + ], + "Estonian": [ + "a", + "i", + "e", + "s", + "t", + "l", + "u", + "n", + "o", + "k", + "r", + "d", + "m", + "v", + "g", + "p", + "j", + "h", + "ä", + "b", + "õ", + "ü", + "f", + "c", + "ö", + "y", + ], + "Thai": [ + "า", + "น", + "ร", + "อ", + "ก", + "เ", + "ง", + "ม", + "ย", + "ล", + "ว", + "ด", + "ท", + "ส", + "ต", + "ะ", + "ป", + "บ", + "ค", + "ห", + "แ", + "จ", + "พ", + "ช", + "ข", + "ใ", + ], + "Greek": [ + "α", + "τ", + "ο", + "ι", + "ε", + "ν", + "ρ", + "σ", + "κ", + "η", + "π", + "ς", + "υ", + "μ", + "λ", + "ί", + "ό", + "ά", + "γ", + "έ", + "δ", + "ή", + "ω", + "χ", + "θ", + "ύ", + ], + "Tamil": [ + "க", + "த", + "ப", + "ட", + "à®°", + "à®®", + "ல", + "ன", + "வ", + "à®±", + "ய", + "ள", + "ச", + "ந", + "இ", + "ண", + "அ", + "ஆ", + "à®´", + "ங", + "எ", + "உ", + "ஒ", + "ஸ", + ], + "Kazakh": [ + "а", + "ы", + "е", + "н", + "т", + "р", + "л", + "і", + "д", + "с", + "м", + "қ", + "к", + "о", + "б", + "и", + "у", + "ғ", + "ж", + "Ò£", + "з", + "ш", + "й", + "п", + "г", + "Ó©", + ], +} + +LANGUAGE_SUPPORTED_COUNT: int = len(FREQUENCIES) diff --git a/.venv/Lib/site-packages/charset_normalizer/legacy.py b/.venv/Lib/site-packages/charset_normalizer/legacy.py new file mode 100644 index 0000000..e221bec --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/legacy.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any +from warnings import warn + +from .api import from_bytes +from .constant import CHARDET_CORRESPONDENCE + +# TODO: remove this check when dropping Python 3.7 support +if TYPE_CHECKING: + from typing_extensions import TypedDict + + class ResultDict(TypedDict): + encoding: str | None + language: str + confidence: float | None + + +def detect( + byte_str: bytes, should_rename_legacy: bool = False, **kwargs: Any +) -> ResultDict: + """ + chardet legacy method + Detect the encoding of the given byte string. It should be mostly backward-compatible. + Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it) + This function is deprecated and should be used to migrate your project easily, consult the documentation for + further information. Not planned for removal. + + :param byte_str: The byte sequence to examine. + :param should_rename_legacy: Should we rename legacy encodings + to their more modern equivalents? + """ + if len(kwargs): + warn( + f"charset-normalizer disregard arguments '{','.join(list(kwargs.keys()))}' in legacy function detect()" + ) + + if not isinstance(byte_str, (bytearray, bytes)): + raise TypeError( # pragma: nocover + f"Expected object of type bytes or bytearray, got: {type(byte_str)}" + ) + + if isinstance(byte_str, bytearray): + byte_str = bytes(byte_str) + + r = from_bytes(byte_str).best() + + encoding = r.encoding if r is not None else None + language = r.language if r is not None and r.language != "Unknown" else "" + confidence = 1.0 - r.chaos if r is not None else None + + # Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process + # but chardet does return 'utf-8-sig' and it is a valid codec name. + if r is not None and encoding == "utf_8" and r.bom: + encoding += "_sig" + + if should_rename_legacy is False and encoding in CHARDET_CORRESPONDENCE: + encoding = CHARDET_CORRESPONDENCE[encoding] + + return { + "encoding": encoding, + "language": language, + "confidence": confidence, + } diff --git a/.venv/Lib/site-packages/charset_normalizer/md.cp312-win_amd64.pyd b/.venv/Lib/site-packages/charset_normalizer/md.cp312-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..4c6c2cf5020169b7188e0ce9f1634524209fad6a GIT binary patch literal 10752 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 bool: + """ + Determine if given character should be fed in. + """ + raise NotImplementedError # pragma: nocover + + def feed(self, character: str) -> None: + """ + The main routine to be executed upon character. + Insert the logic in witch the text would be considered chaotic. + """ + raise NotImplementedError # pragma: nocover + + def reset(self) -> None: # pragma: no cover + """ + Permit to reset the plugin to the initial state. + """ + raise NotImplementedError + + @property + def ratio(self) -> float: + """ + Compute the chaos ratio based on what your feed() has seen. + Must NOT be lower than 0.; No restriction gt 0. + """ + raise NotImplementedError # pragma: nocover + + +class TooManySymbolOrPunctuationPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._punctuation_count: int = 0 + self._symbol_count: int = 0 + self._character_count: int = 0 + + self._last_printable_char: str | None = None + self._frenzy_symbol_in_word: bool = False + + def eligible(self, character: str) -> bool: + return character.isprintable() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if ( + character != self._last_printable_char + and character not in COMMON_SAFE_ASCII_CHARACTERS + ): + if is_punctuation(character): + self._punctuation_count += 1 + elif ( + character.isdigit() is False + and is_symbol(character) + and is_emoticon(character) is False + ): + self._symbol_count += 2 + + self._last_printable_char = character + + def reset(self) -> None: # Abstract + self._punctuation_count = 0 + self._character_count = 0 + self._symbol_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + ratio_of_punctuation: float = ( + self._punctuation_count + self._symbol_count + ) / self._character_count + + return ratio_of_punctuation if ratio_of_punctuation >= 0.3 else 0.0 + + +class TooManyAccentuatedPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._character_count: int = 0 + self._accentuated_count: int = 0 + + def eligible(self, character: str) -> bool: + return character.isalpha() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if is_accentuated(character): + self._accentuated_count += 1 + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._accentuated_count = 0 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + ratio_of_accentuation: float = self._accentuated_count / self._character_count + return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0 + + +class UnprintablePlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._unprintable_count: int = 0 + self._character_count: int = 0 + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if is_unprintable(character): + self._unprintable_count += 1 + self._character_count += 1 + + def reset(self) -> None: # Abstract + self._unprintable_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._unprintable_count * 8) / self._character_count + + +class SuspiciousDuplicateAccentPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._successive_count: int = 0 + self._character_count: int = 0 + + self._last_latin_character: str | None = None + + def eligible(self, character: str) -> bool: + return character.isalpha() and is_latin(character) + + def feed(self, character: str) -> None: + self._character_count += 1 + if ( + self._last_latin_character is not None + and is_accentuated(character) + and is_accentuated(self._last_latin_character) + ): + if character.isupper() and self._last_latin_character.isupper(): + self._successive_count += 1 + # Worse if its the same char duplicated with different accent. + if remove_accent(character) == remove_accent(self._last_latin_character): + self._successive_count += 1 + self._last_latin_character = character + + def reset(self) -> None: # Abstract + self._successive_count = 0 + self._character_count = 0 + self._last_latin_character = None + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._successive_count * 2) / self._character_count + + +class SuspiciousRange(MessDetectorPlugin): + def __init__(self) -> None: + self._suspicious_successive_range_count: int = 0 + self._character_count: int = 0 + self._last_printable_seen: str | None = None + + def eligible(self, character: str) -> bool: + return character.isprintable() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if ( + character.isspace() + or is_punctuation(character) + or character in COMMON_SAFE_ASCII_CHARACTERS + ): + self._last_printable_seen = None + return + + if self._last_printable_seen is None: + self._last_printable_seen = character + return + + unicode_range_a: str | None = unicode_range(self._last_printable_seen) + unicode_range_b: str | None = unicode_range(character) + + if is_suspiciously_successive_range(unicode_range_a, unicode_range_b): + self._suspicious_successive_range_count += 1 + + self._last_printable_seen = character + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._suspicious_successive_range_count = 0 + self._last_printable_seen = None + + @property + def ratio(self) -> float: + if self._character_count <= 13: + return 0.0 + + ratio_of_suspicious_range_usage: float = ( + self._suspicious_successive_range_count * 2 + ) / self._character_count + + return ratio_of_suspicious_range_usage + + +class SuperWeirdWordPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._word_count: int = 0 + self._bad_word_count: int = 0 + self._foreign_long_count: int = 0 + + self._is_current_word_bad: bool = False + self._foreign_long_watch: bool = False + + self._character_count: int = 0 + self._bad_character_count: int = 0 + + self._buffer: str = "" + self._buffer_accent_count: int = 0 + self._buffer_glyph_count: int = 0 + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if character.isalpha(): + self._buffer += character + if is_accentuated(character): + self._buffer_accent_count += 1 + if ( + self._foreign_long_watch is False + and (is_latin(character) is False or is_accentuated(character)) + and is_cjk(character) is False + and is_hangul(character) is False + and is_katakana(character) is False + and is_hiragana(character) is False + and is_thai(character) is False + ): + self._foreign_long_watch = True + if ( + is_cjk(character) + or is_hangul(character) + or is_katakana(character) + or is_hiragana(character) + or is_thai(character) + ): + self._buffer_glyph_count += 1 + return + if not self._buffer: + return + if ( + character.isspace() or is_punctuation(character) or is_separator(character) + ) and self._buffer: + self._word_count += 1 + buffer_length: int = len(self._buffer) + + self._character_count += buffer_length + + if buffer_length >= 4: + if self._buffer_accent_count / buffer_length >= 0.5: + self._is_current_word_bad = True + # Word/Buffer ending with an upper case accentuated letter are so rare, + # that we will consider them all as suspicious. Same weight as foreign_long suspicious. + elif ( + is_accentuated(self._buffer[-1]) + and self._buffer[-1].isupper() + and all(_.isupper() for _ in self._buffer) is False + ): + self._foreign_long_count += 1 + self._is_current_word_bad = True + elif self._buffer_glyph_count == 1: + self._is_current_word_bad = True + self._foreign_long_count += 1 + if buffer_length >= 24 and self._foreign_long_watch: + camel_case_dst = [ + i + for c, i in zip(self._buffer, range(0, buffer_length)) + if c.isupper() + ] + probable_camel_cased: bool = False + + if camel_case_dst and (len(camel_case_dst) / buffer_length <= 0.3): + probable_camel_cased = True + + if not probable_camel_cased: + self._foreign_long_count += 1 + self._is_current_word_bad = True + + if self._is_current_word_bad: + self._bad_word_count += 1 + self._bad_character_count += len(self._buffer) + self._is_current_word_bad = False + + self._foreign_long_watch = False + self._buffer = "" + self._buffer_accent_count = 0 + self._buffer_glyph_count = 0 + elif ( + character not in {"<", ">", "-", "=", "~", "|", "_"} + and character.isdigit() is False + and is_symbol(character) + ): + self._is_current_word_bad = True + self._buffer += character + + def reset(self) -> None: # Abstract + self._buffer = "" + self._is_current_word_bad = False + self._foreign_long_watch = False + self._bad_word_count = 0 + self._word_count = 0 + self._character_count = 0 + self._bad_character_count = 0 + self._foreign_long_count = 0 + + @property + def ratio(self) -> float: + if self._word_count <= 10 and self._foreign_long_count == 0: + return 0.0 + + return self._bad_character_count / self._character_count + + +class CjkUncommonPlugin(MessDetectorPlugin): + """ + Detect messy CJK text that probably means nothing. + """ + + def __init__(self) -> None: + self._character_count: int = 0 + self._uncommon_count: int = 0 + + def eligible(self, character: str) -> bool: + return is_cjk(character) + + def feed(self, character: str) -> None: + self._character_count += 1 + + if is_cjk_uncommon(character): + self._uncommon_count += 1 + return + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._uncommon_count = 0 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + uncommon_form_usage: float = self._uncommon_count / self._character_count + + # we can be pretty sure it's garbage when uncommon characters are widely + # used. otherwise it could just be traditional chinese for example. + return uncommon_form_usage / 10 if uncommon_form_usage > 0.5 else 0.0 + + +class ArchaicUpperLowerPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._buf: bool = False + + self._character_count_since_last_sep: int = 0 + + self._successive_upper_lower_count: int = 0 + self._successive_upper_lower_count_final: int = 0 + + self._character_count: int = 0 + + self._last_alpha_seen: str | None = None + self._current_ascii_only: bool = True + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + is_concerned = character.isalpha() and is_case_variable(character) + chunk_sep = is_concerned is False + + if chunk_sep and self._character_count_since_last_sep > 0: + if ( + self._character_count_since_last_sep <= 64 + and character.isdigit() is False + and self._current_ascii_only is False + ): + self._successive_upper_lower_count_final += ( + self._successive_upper_lower_count + ) + + self._successive_upper_lower_count = 0 + self._character_count_since_last_sep = 0 + self._last_alpha_seen = None + self._buf = False + self._character_count += 1 + self._current_ascii_only = True + + return + + if self._current_ascii_only is True and character.isascii() is False: + self._current_ascii_only = False + + if self._last_alpha_seen is not None: + if (character.isupper() and self._last_alpha_seen.islower()) or ( + character.islower() and self._last_alpha_seen.isupper() + ): + if self._buf is True: + self._successive_upper_lower_count += 2 + self._buf = False + else: + self._buf = True + else: + self._buf = False + + self._character_count += 1 + self._character_count_since_last_sep += 1 + self._last_alpha_seen = character + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._character_count_since_last_sep = 0 + self._successive_upper_lower_count = 0 + self._successive_upper_lower_count_final = 0 + self._last_alpha_seen = None + self._buf = False + self._current_ascii_only = True + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return self._successive_upper_lower_count_final / self._character_count + + +class ArabicIsolatedFormPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._character_count: int = 0 + self._isolated_form_count: int = 0 + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._isolated_form_count = 0 + + def eligible(self, character: str) -> bool: + return is_arabic(character) + + def feed(self, character: str) -> None: + self._character_count += 1 + + if is_arabic_isolated_form(character): + self._isolated_form_count += 1 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + isolated_form_usage: float = self._isolated_form_count / self._character_count + + return isolated_form_usage + + +@lru_cache(maxsize=1024) +def is_suspiciously_successive_range( + unicode_range_a: str | None, unicode_range_b: str | None +) -> bool: + """ + Determine if two Unicode range seen next to each other can be considered as suspicious. + """ + if unicode_range_a is None or unicode_range_b is None: + return True + + if unicode_range_a == unicode_range_b: + return False + + if "Latin" in unicode_range_a and "Latin" in unicode_range_b: + return False + + if "Emoticons" in unicode_range_a or "Emoticons" in unicode_range_b: + return False + + # Latin characters can be accompanied with a combining diacritical mark + # eg. Vietnamese. + if ("Latin" in unicode_range_a or "Latin" in unicode_range_b) and ( + "Combining" in unicode_range_a or "Combining" in unicode_range_b + ): + return False + + keywords_range_a, keywords_range_b = ( + unicode_range_a.split(" "), + unicode_range_b.split(" "), + ) + + for el in keywords_range_a: + if el in UNICODE_SECONDARY_RANGE_KEYWORD: + continue + if el in keywords_range_b: + return False + + # Japanese Exception + range_a_jp_chars, range_b_jp_chars = ( + unicode_range_a + in ( + "Hiragana", + "Katakana", + ), + unicode_range_b in ("Hiragana", "Katakana"), + ) + if (range_a_jp_chars or range_b_jp_chars) and ( + "CJK" in unicode_range_a or "CJK" in unicode_range_b + ): + return False + if range_a_jp_chars and range_b_jp_chars: + return False + + if "Hangul" in unicode_range_a or "Hangul" in unicode_range_b: + if "CJK" in unicode_range_a or "CJK" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + # Chinese/Japanese use dedicated range for punctuation and/or separators. + if ("CJK" in unicode_range_a or "CJK" in unicode_range_b) or ( + unicode_range_a in ["Katakana", "Hiragana"] + and unicode_range_b in ["Katakana", "Hiragana"] + ): + if "Punctuation" in unicode_range_a or "Punctuation" in unicode_range_b: + return False + if "Forms" in unicode_range_a or "Forms" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + return True + + +@lru_cache(maxsize=2048) +def mess_ratio( + decoded_sequence: str, maximum_threshold: float = 0.2, debug: bool = False +) -> float: + """ + Compute a mess ratio given a decoded bytes sequence. The maximum threshold does stop the computation earlier. + """ + + detectors: list[MessDetectorPlugin] = [ + md_class() for md_class in MessDetectorPlugin.__subclasses__() + ] + + length: int = len(decoded_sequence) + 1 + + mean_mess_ratio: float = 0.0 + + if length < 512: + intermediary_mean_mess_ratio_calc: int = 32 + elif length <= 1024: + intermediary_mean_mess_ratio_calc = 64 + else: + intermediary_mean_mess_ratio_calc = 128 + + for character, index in zip(decoded_sequence + "\n", range(length)): + for detector in detectors: + if detector.eligible(character): + detector.feed(character) + + if ( + index > 0 and index % intermediary_mean_mess_ratio_calc == 0 + ) or index == length - 1: + mean_mess_ratio = sum(dt.ratio for dt in detectors) + + if mean_mess_ratio >= maximum_threshold: + break + + if debug: + logger = getLogger("charset_normalizer") + + logger.log( + TRACE, + "Mess-detector extended-analysis start. " + f"intermediary_mean_mess_ratio_calc={intermediary_mean_mess_ratio_calc} mean_mess_ratio={mean_mess_ratio} " + f"maximum_threshold={maximum_threshold}", + ) + + if len(decoded_sequence) > 16: + logger.log(TRACE, f"Starting with: {decoded_sequence[:16]}") + logger.log(TRACE, f"Ending with: {decoded_sequence[-16::]}") + + for dt in detectors: + logger.log(TRACE, f"{dt.__class__}: {dt.ratio}") + + return round(mean_mess_ratio, 3) diff --git a/.venv/Lib/site-packages/charset_normalizer/md__mypyc.cp312-win_amd64.pyd b/.venv/Lib/site-packages/charset_normalizer/md__mypyc.cp312-win_amd64.pyd new file mode 100644 index 0000000000000000000000000000000000000000..18e8e829786a3224858e869563c85f2711113adf GIT binary patch literal 125952 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%0Am0DUX*Y%Hp^Calgw?`Ds7v=X}Ta{b7H^ANMEx z7^4^)7$0b_bX2-3$;wz|vXU9h4*G*bgA;>OgQ;q|nyu!lPSvjtS4XPj)rqRq+H1*L zPi?9u^+-KdPuDZ`@%lu4vfkbZH98v6My!!-89L_n+^81&XW;l$(TFo zPPsxX!=*?mUg|0(m<2thOsTKb&kP6!dw0Ye^V->)x))=_GVN!G;ShT=G7zdAVFm}l#`x>NV-&H6}vv>t9m8u3O~ zBh^TMBKk7UXTObmh`2|cn3Hglj^p^wq%-A&@*TuFmQUo9`5xw8U%o$x_9W2`6*>yh zLhR$1Hw#0BNU?{RILvVrayy7J9II}WY?g{4OWO<6{ zCH!PC11J3{KThoW2Ks}TMJusNwvr2C6dsHWrkRDg!O_97!7y>^Axb(A`>OrHTx?c{ zg84XF9kbD!3g)FoFkFk+D0bBnHj+KHjE!c0&9M<3u8r8JPShrY$hOx*^^SU&Ssi6w m$Ln2n6YF%n$Hu#_-fv?*RF_73Bhg3(^PGA8|G)oQ1pWn=iNkUL literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/charset_normalizer/models.py b/.venv/Lib/site-packages/charset_normalizer/models.py new file mode 100644 index 0000000..1042758 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/models.py @@ -0,0 +1,360 @@ +from __future__ import annotations + +from encodings.aliases import aliases +from hashlib import sha256 +from json import dumps +from re import sub +from typing import Any, Iterator, List, Tuple + +from .constant import RE_POSSIBLE_ENCODING_INDICATION, TOO_BIG_SEQUENCE +from .utils import iana_name, is_multi_byte_encoding, unicode_range + + +class CharsetMatch: + def __init__( + self, + payload: bytes, + guessed_encoding: str, + mean_mess_ratio: float, + has_sig_or_bom: bool, + languages: CoherenceMatches, + decoded_payload: str | None = None, + preemptive_declaration: str | None = None, + ): + self._payload: bytes = payload + + self._encoding: str = guessed_encoding + self._mean_mess_ratio: float = mean_mess_ratio + self._languages: CoherenceMatches = languages + self._has_sig_or_bom: bool = has_sig_or_bom + self._unicode_ranges: list[str] | None = None + + self._leaves: list[CharsetMatch] = [] + self._mean_coherence_ratio: float = 0.0 + + self._output_payload: bytes | None = None + self._output_encoding: str | None = None + + self._string: str | None = decoded_payload + + self._preemptive_declaration: str | None = preemptive_declaration + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CharsetMatch): + if isinstance(other, str): + return iana_name(other) == self.encoding + return False + return self.encoding == other.encoding and self.fingerprint == other.fingerprint + + def __lt__(self, other: object) -> bool: + """ + Implemented to make sorted available upon CharsetMatches items. + """ + if not isinstance(other, CharsetMatch): + raise ValueError + + chaos_difference: float = abs(self.chaos - other.chaos) + coherence_difference: float = abs(self.coherence - other.coherence) + + # Below 1% difference --> Use Coherence + if chaos_difference < 0.01 and coherence_difference > 0.02: + return self.coherence > other.coherence + elif chaos_difference < 0.01 and coherence_difference <= 0.02: + # When having a difficult decision, use the result that decoded as many multi-byte as possible. + # preserve RAM usage! + if len(self._payload) >= TOO_BIG_SEQUENCE: + return self.chaos < other.chaos + return self.multi_byte_usage > other.multi_byte_usage + + return self.chaos < other.chaos + + @property + def multi_byte_usage(self) -> float: + return 1.0 - (len(str(self)) / len(self.raw)) + + def __str__(self) -> str: + # Lazy Str Loading + if self._string is None: + self._string = str(self._payload, self._encoding, "strict") + return self._string + + def __repr__(self) -> str: + return f"" + + def add_submatch(self, other: CharsetMatch) -> None: + if not isinstance(other, CharsetMatch) or other == self: + raise ValueError( + "Unable to add instance <{}> as a submatch of a CharsetMatch".format( + other.__class__ + ) + ) + + other._string = None # Unload RAM usage; dirty trick. + self._leaves.append(other) + + @property + def encoding(self) -> str: + return self._encoding + + @property + def encoding_aliases(self) -> list[str]: + """ + Encoding name are known by many name, using this could help when searching for IBM855 when it's listed as CP855. + """ + also_known_as: list[str] = [] + for u, p in aliases.items(): + if self.encoding == u: + also_known_as.append(p) + elif self.encoding == p: + also_known_as.append(u) + return also_known_as + + @property + def bom(self) -> bool: + return self._has_sig_or_bom + + @property + def byte_order_mark(self) -> bool: + return self._has_sig_or_bom + + @property + def languages(self) -> list[str]: + """ + Return the complete list of possible languages found in decoded sequence. + Usually not really useful. Returned list may be empty even if 'language' property return something != 'Unknown'. + """ + return [e[0] for e in self._languages] + + @property + def language(self) -> str: + """ + Most probable language found in decoded sequence. If none were detected or inferred, the property will return + "Unknown". + """ + if not self._languages: + # Trying to infer the language based on the given encoding + # Its either English or we should not pronounce ourselves in certain cases. + if "ascii" in self.could_be_from_charset: + return "English" + + # doing it there to avoid circular import + from charset_normalizer.cd import encoding_languages, mb_encoding_languages + + languages = ( + mb_encoding_languages(self.encoding) + if is_multi_byte_encoding(self.encoding) + else encoding_languages(self.encoding) + ) + + if len(languages) == 0 or "Latin Based" in languages: + return "Unknown" + + return languages[0] + + return self._languages[0][0] + + @property + def chaos(self) -> float: + return self._mean_mess_ratio + + @property + def coherence(self) -> float: + if not self._languages: + return 0.0 + return self._languages[0][1] + + @property + def percent_chaos(self) -> float: + return round(self.chaos * 100, ndigits=3) + + @property + def percent_coherence(self) -> float: + return round(self.coherence * 100, ndigits=3) + + @property + def raw(self) -> bytes: + """ + Original untouched bytes. + """ + return self._payload + + @property + def submatch(self) -> list[CharsetMatch]: + return self._leaves + + @property + def has_submatch(self) -> bool: + return len(self._leaves) > 0 + + @property + def alphabets(self) -> list[str]: + if self._unicode_ranges is not None: + return self._unicode_ranges + # list detected ranges + detected_ranges: list[str | None] = [unicode_range(char) for char in str(self)] + # filter and sort + self._unicode_ranges = sorted(list({r for r in detected_ranges if r})) + return self._unicode_ranges + + @property + def could_be_from_charset(self) -> list[str]: + """ + The complete list of encoding that output the exact SAME str result and therefore could be the originating + encoding. + This list does include the encoding available in property 'encoding'. + """ + return [self._encoding] + [m.encoding for m in self._leaves] + + def output(self, encoding: str = "utf_8") -> bytes: + """ + Method to get re-encoded bytes payload using given target encoding. Default to UTF-8. + Any errors will be simply ignored by the encoder NOT replaced. + """ + if self._output_encoding is None or self._output_encoding != encoding: + self._output_encoding = encoding + decoded_string = str(self) + if ( + self._preemptive_declaration is not None + and self._preemptive_declaration.lower() + not in ["utf-8", "utf8", "utf_8"] + ): + patched_header = sub( + RE_POSSIBLE_ENCODING_INDICATION, + lambda m: m.string[m.span()[0] : m.span()[1]].replace( + m.groups()[0], + iana_name(self._output_encoding).replace("_", "-"), # type: ignore[arg-type] + ), + decoded_string[:8192], + count=1, + ) + + decoded_string = patched_header + decoded_string[8192:] + + self._output_payload = decoded_string.encode(encoding, "replace") + + return self._output_payload # type: ignore + + @property + def fingerprint(self) -> str: + """ + Retrieve the unique SHA256 computed using the transformed (re-encoded) payload. Not the original one. + """ + return sha256(self.output()).hexdigest() + + +class CharsetMatches: + """ + Container with every CharsetMatch items ordered by default from most probable to the less one. + Act like a list(iterable) but does not implements all related methods. + """ + + def __init__(self, results: list[CharsetMatch] | None = None): + self._results: list[CharsetMatch] = sorted(results) if results else [] + + def __iter__(self) -> Iterator[CharsetMatch]: + yield from self._results + + def __getitem__(self, item: int | str) -> CharsetMatch: + """ + Retrieve a single item either by its position or encoding name (alias may be used here). + Raise KeyError upon invalid index or encoding not present in results. + """ + if isinstance(item, int): + return self._results[item] + if isinstance(item, str): + item = iana_name(item, False) + for result in self._results: + if item in result.could_be_from_charset: + return result + raise KeyError + + def __len__(self) -> int: + return len(self._results) + + def __bool__(self) -> bool: + return len(self._results) > 0 + + def append(self, item: CharsetMatch) -> None: + """ + Insert a single match. Will be inserted accordingly to preserve sort. + Can be inserted as a submatch. + """ + if not isinstance(item, CharsetMatch): + raise ValueError( + "Cannot append instance '{}' to CharsetMatches".format( + str(item.__class__) + ) + ) + # We should disable the submatch factoring when the input file is too heavy (conserve RAM usage) + if len(item.raw) < TOO_BIG_SEQUENCE: + for match in self._results: + if match.fingerprint == item.fingerprint and match.chaos == item.chaos: + match.add_submatch(item) + return + self._results.append(item) + self._results = sorted(self._results) + + def best(self) -> CharsetMatch | None: + """ + Simply return the first match. Strict equivalent to matches[0]. + """ + if not self._results: + return None + return self._results[0] + + def first(self) -> CharsetMatch | None: + """ + Redundant method, call the method best(). Kept for BC reasons. + """ + return self.best() + + +CoherenceMatch = Tuple[str, float] +CoherenceMatches = List[CoherenceMatch] + + +class CliDetectionResult: + def __init__( + self, + path: str, + encoding: str | None, + encoding_aliases: list[str], + alternative_encodings: list[str], + language: str, + alphabets: list[str], + has_sig_or_bom: bool, + chaos: float, + coherence: float, + unicode_path: str | None, + is_preferred: bool, + ): + self.path: str = path + self.unicode_path: str | None = unicode_path + self.encoding: str | None = encoding + self.encoding_aliases: list[str] = encoding_aliases + self.alternative_encodings: list[str] = alternative_encodings + self.language: str = language + self.alphabets: list[str] = alphabets + self.has_sig_or_bom: bool = has_sig_or_bom + self.chaos: float = chaos + self.coherence: float = coherence + self.is_preferred: bool = is_preferred + + @property + def __dict__(self) -> dict[str, Any]: # type: ignore + return { + "path": self.path, + "encoding": self.encoding, + "encoding_aliases": self.encoding_aliases, + "alternative_encodings": self.alternative_encodings, + "language": self.language, + "alphabets": self.alphabets, + "has_sig_or_bom": self.has_sig_or_bom, + "chaos": self.chaos, + "coherence": self.coherence, + "unicode_path": self.unicode_path, + "is_preferred": self.is_preferred, + } + + def to_json(self) -> str: + return dumps(self.__dict__, ensure_ascii=True, indent=4) diff --git a/.venv/Lib/site-packages/charset_normalizer/py.typed b/.venv/Lib/site-packages/charset_normalizer/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/charset_normalizer/utils.py b/.venv/Lib/site-packages/charset_normalizer/utils.py new file mode 100644 index 0000000..6bf0384 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/utils.py @@ -0,0 +1,414 @@ +from __future__ import annotations + +import importlib +import logging +import unicodedata +from codecs import IncrementalDecoder +from encodings.aliases import aliases +from functools import lru_cache +from re import findall +from typing import Generator + +from _multibytecodec import ( # type: ignore[import-not-found,import] + MultibyteIncrementalDecoder, +) + +from .constant import ( + ENCODING_MARKS, + IANA_SUPPORTED_SIMILAR, + RE_POSSIBLE_ENCODING_INDICATION, + UNICODE_RANGES_COMBINED, + UNICODE_SECONDARY_RANGE_KEYWORD, + UTF8_MAXIMAL_ALLOCATION, + COMMON_CJK_CHARACTERS, +) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_accentuated(character: str) -> bool: + try: + description: str = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + return ( + "WITH GRAVE" in description + or "WITH ACUTE" in description + or "WITH CEDILLA" in description + or "WITH DIAERESIS" in description + or "WITH CIRCUMFLEX" in description + or "WITH TILDE" in description + or "WITH MACRON" in description + or "WITH RING ABOVE" in description + ) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def remove_accent(character: str) -> str: + decomposed: str = unicodedata.decomposition(character) + if not decomposed: + return character + + codes: list[str] = decomposed.split(" ") + + return chr(int(codes[0], 16)) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def unicode_range(character: str) -> str | None: + """ + Retrieve the Unicode range official name from a single character. + """ + character_ord: int = ord(character) + + for range_name, ord_range in UNICODE_RANGES_COMBINED.items(): + if character_ord in ord_range: + return range_name + + return None + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_latin(character: str) -> bool: + try: + description: str = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + return "LATIN" in description + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_punctuation(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "P" in character_category: + return True + + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Punctuation" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_symbol(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "S" in character_category or "N" in character_category: + return True + + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Forms" in character_range and character_category != "Lo" + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_emoticon(character: str) -> bool: + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Emoticons" in character_range or "Pictographs" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_separator(character: str) -> bool: + if character.isspace() or character in {"|", "+", "<", ">"}: + return True + + character_category: str = unicodedata.category(character) + + return "Z" in character_category or character_category in {"Po", "Pd", "Pc"} + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_case_variable(character: str) -> bool: + return character.islower() != character.isupper() + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + + return "CJK" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hiragana(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + + return "HIRAGANA" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_katakana(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + + return "KATAKANA" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hangul(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + + return "HANGUL" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_thai(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + + return "THAI" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + + return "ARABIC" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic_isolated_form(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: # Defensive: unicode database outdated? + return False + + return "ARABIC" in character_name and "ISOLATED FORM" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk_uncommon(character: str) -> bool: + return character not in COMMON_CJK_CHARACTERS + + +@lru_cache(maxsize=len(UNICODE_RANGES_COMBINED)) +def is_unicode_range_secondary(range_name: str) -> bool: + return any(keyword in range_name for keyword in UNICODE_SECONDARY_RANGE_KEYWORD) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_unprintable(character: str) -> bool: + return ( + character.isspace() is False # includes \n \t \r \v + and character.isprintable() is False + and character != "\x1a" # Why? Its the ASCII substitute character. + and character != "\ufeff" # bug discovered in Python, + # Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space. + ) + + +def any_specified_encoding(sequence: bytes, search_zone: int = 8192) -> str | None: + """ + Extract using ASCII-only decoder any specified encoding in the first n-bytes. + """ + if not isinstance(sequence, bytes): + raise TypeError + + seq_len: int = len(sequence) + + results: list[str] = findall( + RE_POSSIBLE_ENCODING_INDICATION, + sequence[: min(seq_len, search_zone)].decode("ascii", errors="ignore"), + ) + + if len(results) == 0: + return None + + for specified_encoding in results: + specified_encoding = specified_encoding.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if encoding_alias == specified_encoding: + return encoding_iana + if encoding_iana == specified_encoding: + return encoding_iana + + return None + + +@lru_cache(maxsize=128) +def is_multi_byte_encoding(name: str) -> bool: + """ + Verify is a specific encoding is a multi byte one based on it IANA name + """ + return name in { + "utf_8", + "utf_8_sig", + "utf_16", + "utf_16_be", + "utf_16_le", + "utf_32", + "utf_32_le", + "utf_32_be", + "utf_7", + } or issubclass( + importlib.import_module(f"encodings.{name}").IncrementalDecoder, + MultibyteIncrementalDecoder, + ) + + +def identify_sig_or_bom(sequence: bytes) -> tuple[str | None, bytes]: + """ + Identify and extract SIG/BOM in given sequence. + """ + + for iana_encoding in ENCODING_MARKS: + marks: bytes | list[bytes] = ENCODING_MARKS[iana_encoding] + + if isinstance(marks, bytes): + marks = [marks] + + for mark in marks: + if sequence.startswith(mark): + return iana_encoding, mark + + return None, b"" + + +def should_strip_sig_or_bom(iana_encoding: str) -> bool: + return iana_encoding not in {"utf_16", "utf_32"} + + +def iana_name(cp_name: str, strict: bool = True) -> str: + """Returns the Python normalized encoding name (Not the IANA official name).""" + cp_name = cp_name.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if cp_name in [encoding_alias, encoding_iana]: + return encoding_iana + + if strict: + raise ValueError(f"Unable to retrieve IANA for '{cp_name}'") + + return cp_name + + +def cp_similarity(iana_name_a: str, iana_name_b: str) -> float: + if is_multi_byte_encoding(iana_name_a) or is_multi_byte_encoding(iana_name_b): + return 0.0 + + decoder_a = importlib.import_module(f"encodings.{iana_name_a}").IncrementalDecoder + decoder_b = importlib.import_module(f"encodings.{iana_name_b}").IncrementalDecoder + + id_a: IncrementalDecoder = decoder_a(errors="ignore") + id_b: IncrementalDecoder = decoder_b(errors="ignore") + + character_match_count: int = 0 + + for i in range(255): + to_be_decoded: bytes = bytes([i]) + if id_a.decode(to_be_decoded) == id_b.decode(to_be_decoded): + character_match_count += 1 + + return character_match_count / 254 + + +def is_cp_similar(iana_name_a: str, iana_name_b: str) -> bool: + """ + Determine if two code page are at least 80% similar. IANA_SUPPORTED_SIMILAR dict was generated using + the function cp_similarity. + """ + return ( + iana_name_a in IANA_SUPPORTED_SIMILAR + and iana_name_b in IANA_SUPPORTED_SIMILAR[iana_name_a] + ) + + +def set_logging_handler( + name: str = "charset_normalizer", + level: int = logging.INFO, + format_string: str = "%(asctime)s | %(levelname)s | %(message)s", +) -> None: + logger = logging.getLogger(name) + logger.setLevel(level) + + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter(format_string)) + logger.addHandler(handler) + + +def cut_sequence_chunks( + sequences: bytes, + encoding_iana: str, + offsets: range, + chunk_size: int, + bom_or_sig_available: bool, + strip_sig_or_bom: bool, + sig_payload: bytes, + is_multi_byte_decoder: bool, + decoded_payload: str | None = None, +) -> Generator[str, None, None]: + if decoded_payload and is_multi_byte_decoder is False: + for i in offsets: + chunk = decoded_payload[i : i + chunk_size] + if not chunk: + break + yield chunk + else: + for i in offsets: + chunk_end = i + chunk_size + if chunk_end > len(sequences) + 8: + continue + + cut_sequence = sequences[i : i + chunk_size] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode( + encoding_iana, + errors="ignore" if is_multi_byte_decoder else "strict", + ) + + # multi-byte bad cutting detector and adjustment + # not the cleanest way to perform that fix but clever enough for now. + if is_multi_byte_decoder and i > 0: + chunk_partial_size_chk: int = min(chunk_size, 16) + + if ( + decoded_payload + and chunk[:chunk_partial_size_chk] not in decoded_payload + ): + for j in range(i, i - 4, -1): + cut_sequence = sequences[j:chunk_end] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode(encoding_iana, errors="ignore") + + if chunk[:chunk_partial_size_chk] in decoded_payload: + break + + yield chunk diff --git a/.venv/Lib/site-packages/charset_normalizer/version.py b/.venv/Lib/site-packages/charset_normalizer/version.py new file mode 100644 index 0000000..e5687e3 --- /dev/null +++ b/.venv/Lib/site-packages/charset_normalizer/version.py @@ -0,0 +1,8 @@ +""" +Expose version +""" + +from __future__ import annotations + +__version__ = "3.4.2" +VERSION = __version__.split(".") diff --git a/.venv/Lib/site-packages/click-8.2.1.dist-info/INSTALLER b/.venv/Lib/site-packages/click-8.2.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/click-8.2.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/click-8.2.1.dist-info/METADATA b/.venv/Lib/site-packages/click-8.2.1.dist-info/METADATA new file mode 100644 index 0000000..e6c05af --- /dev/null +++ b/.venv/Lib/site-packages/click-8.2.1.dist-info/METADATA @@ -0,0 +1,82 @@ +Metadata-Version: 2.4 +Name: click +Version: 8.2.1 +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/.venv/Lib/site-packages/click-8.2.1.dist-info/RECORD b/.venv/Lib/site-packages/click-8.2.1.dist-info/RECORD new file mode 100644 index 0000000..dda88a8 --- /dev/null +++ b/.venv/Lib/site-packages/click-8.2.1.dist-info/RECORD @@ -0,0 +1,38 @@ +click-8.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +click-8.2.1.dist-info/METADATA,sha256=dI1MbhHTLoKD2tNCCGnx9rK2gok23HDNylFeLKdLSik,2471 +click-8.2.1.dist-info/RECORD,, +click-8.2.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +click-8.2.1.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__/_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=ASXhLi9IQIc0Js9KQSS-3-SLZcPet3VqysBf9WgbbpI,26712 +click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400 +click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465 +click/core.py,sha256=gUhpNS9cFBGdEXXdisGVG-eRvGf49RTyFagxulqwdFw,117343 +click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461 +click/exceptions.py,sha256=1rdtXgHJ1b3OjGkN-UpXB9t_HCBihJvh_DtpmLmwn9s,9891 +click/formatting.py,sha256=Bhqx4QXdKQ9W4WKknIwj5KPKFmtduGOuGq1yw_THLZ8,9726 +click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923 +click/parser.py,sha256=nU1Ah2p11q29ul1vNdU9swPo_PUuKrxU6YXToi71q1c,18979 +click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click/shell_completion.py,sha256=CQSGdjgun4ORbOZrXP0CVhEtPx4knsufOkRsDiK64cM,19857 +click/termui.py,sha256=vAYrKC2a7f_NfEIhAThEVYfa__ib5XQbTSCGtJlABRA,30847 +click/testing.py,sha256=2eLdAaCJCGToP5Tw-XN8JjrDb3wbJIfARxg3d0crW5M,18702 +click/types.py,sha256=KBTRxN28cR1VZ5mb9iJX98MQSw_p9SGzljqfEI8z5Tw,38389 +click/utils.py,sha256=b1Mm-usEDBHtEwcPltPIn3zSK4nw2KTp5GC7_oSTlLo,20245 diff --git a/.venv/Lib/site-packages/click-8.2.1.dist-info/WHEEL b/.venv/Lib/site-packages/click-8.2.1.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/.venv/Lib/site-packages/click-8.2.1.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/.venv/Lib/site-packages/click-8.2.1.dist-info/licenses/LICENSE.txt b/.venv/Lib/site-packages/click-8.2.1.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..d12a849 --- /dev/null +++ b/.venv/Lib/site-packages/click-8.2.1.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/.venv/Lib/site-packages/click/__init__.py b/.venv/Lib/site-packages/click/__init__.py new file mode 100644 index 0000000..1aa547c --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/click/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43d167c6201aeb4ca06b2bce0887e23e12739e84 GIT binary patch literal 4088 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 O{F&gv+myv*&i@6QpN*&h literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47603d823b742c57906c3365e17020a0cab85e58 GIT binary patch literal 31493 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e282c8d011b98adfe7c1c96a4773bae92e046f25 GIT binary patch literal 2436 zcmaJ@+fUn87(c$maV`)Dmo`9X3Wbrig|4@0wAw^L-OwiJ)J@aURF<*blEks;u>*uU zQZ?}qwn^n_WU3~yr>#Q!Bl@~Y6QO0v+*Lx_CarJL=%lKew0$QrG|+A*(&yhf-|bv} z=R5hKp}~uw-Ia!?=UoW>N;*3Y`lM0YF_0@rMlz+MG!>&Lf@zgbGcne|u9yqP7?n%A zV{Qu3=xrpki%51Uu4Gd+%pS4E)VeV~OGmlKq!XoVpQvfNDVhmgGbW(lDCwH1%$sRZ z6R#^+st@4=ptdi-=n7I$jFM4|_MjJkvP-5G*%+g+GAGlE?3xn?rtFp(&~RBc%GST<~qY=|b?%#1!0_1LVTsMqWOR%R7! zD6$|;XS9@I^HK&2YC=SW$H8W%L!SDT*7)%kxV0g5dw@{8js8xk&6&46@N#RQVNcY z=}A34IIC#0@%Iu_aU)?W$02VDcw@vRH6f*j1VW`bIN!mUtlcb3i<+#$ymnT)5UxA_ z!57qIss;ve3q5H>?V&>3>VYp0eS7$R+p)ZN%NHp5A{)L)xus|IWU-|`KUNO5FW)S9 z*Mq(JQGyqiZ`QEC_4ee_WMTAyKT_`L%TE?AZ1@lT+#D)9ipf&fP_b+1e%EW8{*x63 z1v|DOE0XG2MHtyj82Jl;cM$~_0C-2t_O%@Saz5r53(Zq6^RTFe29izKja0R)&+ce; zEqCvO2kOEX^a|7m3-Z=QfNa99RTifHfBHEz&wPyLsPC9LDoRa6X}d9@CCr4V3JFbC zG}CT2GE;_fBLmnOin6#7re$-EMNn5|Q0?(*BV#Xk8e}gC1@QqAzz-b3q7!xDU_B-3 zHJxBeQM}}1)U`KXJ4~icc6scL0{3&3)AOsu<*1befvzI zYwcQHCZ!7#M%t7&%Gn-8%cK=7nhGg4YQiLS$iU=`a2FANV#+4JoV1Fqj!+{)7%Ygz(iECFq)(T{uwBhdRXo@svQ^Lgj`)p8hS=vwHfQx4wSs ze(&j@LT8r7^P|hH<+f0%ZJ^jTkiWPU2rj?>>0CJ!E`^4Qp`m4N%inl=d}+MUUf@?- zSGo1}S2q0vl?K#tw$g-}T5o5UvW4Jhxplq^lUHOS>cs*0KJ%yv!@Odc_$nsPEIvv^ zGYD9iX5t{}9wS{2D`p03Hanqf3S1YmSepSyd>qh1izH4-gX1RIMjc-vMpwbApZE_E zL9D7M>b7}7(8RPN2)0)c(z=|1Ix5(HLAa3-)tUw$B=&?ZI6}m$Anu~-+^ZK2lYOoV z9)NKP#3HKDO|Da0p`MBhz7-C&bXD90@hI3<@et%iO)V84L4M?I+-@M~2;y4*q`j_l z+YF!zK~E0)T;W1Ad z=O2ign5r$NI_*;s;Q3N){(Nl@-o(S8Aa%~@1yNxrih6{4en;I8QQ#5kd%_*1!pj4t OV1F^#{}+NzHORl*JU&kV literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..937608a90c148ccc935fe1c6d43a640bc7c8e412 GIT binary patch literal 11781 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 G(EkCQ&m%qn literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/core.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07f8be8abd16327717c0db7f78f549033a99e81c GIT binary patch literal 127413 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%0Ab4&JEu65J>q9MQ%d4OiLeC3M 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 TGkR`S;>hZk^1N~#&+PvNY#P@V literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/decorators.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/decorators.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..541382cfac5a4431e77c5e3b852d4bc90eb3c595 GIT binary patch literal 22153 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/exceptions.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d92e86b618d3c1da8677e5fd9c0d05bd657f7ce GIT binary patch literal 14793 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 Qe15(wxBqhvC&m>219^R-kpKVy literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/formatting.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/formatting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec3b9f80b3d7228cace618310f7ce0c5fd38177b GIT binary patch literal 13614 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/globals.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/globals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a01d8af832467309e29e173bc4b82cfca5fa52db GIT binary patch literal 2981 zcmbVOO>7&-6`ox#DT<^-Mv)!YO_Xt(pE7Y-$3=fY6m@OIYLpm?VW|fzEU4kmP+V!b zOUw*OQBYdRHQ)w4q&Mf#LkkpbT=&ppue}xRMTQ&L-P(YQuAwLsw>+V^IcGOfBj z^Z}fmpEqyb@AvqJiHRHmy5?3}-yb976I=|*i6_?H?_qM4aKbI0w5?^!!ny6+%PIJ# ze5ajWPFqBz)(XR`kG?X)9iH~b+S%o-MQkF{*OSBLakw%4h;umO1wO$umml%v{|nzF zFY>WbKDb%r*~{tWJTLL_%g%CvKgDzKo#0daX`Y9Z$$?#-zK>6pttiieAk<8IVW1XZ zo%TbQ`S-2(bZj;ZwAj!tu16_Fh_#2#xk?7)nze8ZhA5*vUj$kqGkw^at=7J+gjBUf z)~>xF)VdBkwMCB$dR~TW!quua$CN&OexCLeo%5Mmuf56Kbs^~+;aXU$^hD6Bo%0$s z_t1+9Il;(R4#Rbv=1|%erJ=B;0w= z`mQx_;&0#Tla16x(znWKb_pK1Aaqv-it3i2i94xrHcgybp>(O`DeAI-HU#Y|!6{Re z(LNhc9p)$yb6ueh$cRHi!v;p3Hf7kREEx1#LW-eCkjiK~l!AKg4g}cKu{2)`89;jW z1Uz6gt2VoVTV3ferG~*?pj~|;y6yLZJ}G$m89@0$_+bWWz^$|kV=L2KzA^aaV~hZO21q= z5zDW3m}G4lS7ViWO{#=O_pL0+E(k--q=_}{F~18gS9E0|4B!ZOgK!vbqb$aBgfLD$ z=J~AQ$FURO`zGu9=%mC2zyps-<|#qhlY~^o21M{eH_%>NoRu<^D;2{hu>kJ>3uyD$ z5~c^L1`08VC1k+k$+%D+i!2>EE`UgssT<&m(nQ64{t`6P*`G=9;kK%XSD>wHhD1Pq~Esg6iXlU-tYa$``O`NJp0pUKWf}K_WI4@ z$=&S9f7~e?{qbA7`B(l*Y+(FT@z9;Ance5l+?<;I-BY`TxAsz`bofDr6iWYmFpeA# z6-vMKa+zE!o-U<+GkH3n`fWZ9)5ib>;lTh(1149AhB2lm!O0rJ&jI4r>OO$1L5Dj? zTjY8I6BdEqklK9pJfx?-8uHDVHQh zlwqBY2sm_?l&d#JKs7309So~jJ`W;}!%rQ9VVivF(jRlLZqMEu%WZ$}c5&*1!TW>V?2&u39( z+qPNA&*K?3ci=-4ALda~ayMov<7qL<)$7eJG;2|>OU#cbqX!*mMG})L8rLl;7{|4P z>xrd{=g-z>-#k0}_WZ)P05GA=#yZ4B3pp%%>Mr^0y_9q1)TL8*o#*~CKKa9EwljcwcIwK}ouhvy^8kZ( k;Wn8c**lZ$o%vhlkX70{VOw9@%h}d5f6q-=B@>5#0~LPU*8l(j literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/parser.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/parser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..887d68fd9c94d2d8e3c0be42b36a691313b00954 GIT binary patch literal 20203 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..801918eebe478cc5a794953c5c02b91203f5dee4 GIT binary patch literal 23150 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/termui.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/termui.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f9e9c6a76b4b0b041ace1fa29f8516647f43b7c GIT binary patch literal 34540 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 DNR!`0 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/testing.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1543d8f6e152a09bb5dab2cee7e18ca3d7119ee GIT binary patch literal 26900 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 uDi@n_e%#X`-&rO6;lH#6<(o|>8dKH8)c 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 GxBnmg?<3a$ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/__pycache__/utils.cpython-312.pyc b/.venv/Lib/site-packages/click/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27582e7efea262270bf649ea6935659d0ee826e0 GIT binary patch literal 24885 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/click/_compat.py b/.venv/Lib/site-packages/click/_compat.py new file mode 100644 index 0000000..f2726b9 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/click/_termui_impl.py b/.venv/Lib/site-packages/click/_termui_impl.py new file mode 100644 index 0000000..51fd9bf --- /dev/null +++ b/.venv/Lib/site-packages/click/_termui_impl.py @@ -0,0 +1,839 @@ +""" +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 shutil import which +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: + import shutil + + 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: + 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 + cmd = cmd_parts[0] + cmd_params = cmd_parts[1:] + + cmd_filepath = 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 + cmd = cmd_parts[0] + + cmd_filepath = 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" + 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 = 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/.venv/Lib/site-packages/click/_textwrap.py b/.venv/Lib/site-packages/click/_textwrap.py new file mode 100644 index 0000000..97fbee3 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/click/_winconsole.py b/.venv/Lib/site-packages/click/_winconsole.py new file mode 100644 index 0000000..e56c7c6 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/click/core.py b/.venv/Lib/site-packages/click/core.py new file mode 100644 index 0000000..f57ada6 --- /dev/null +++ b/.venv/Lib/site-packages/click/core.py @@ -0,0 +1,3135 @@ +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 .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 _flag_needs_value +from .parser import _OptionParser +from .parser import _split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import echo +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper + +if t.TYPE_CHECKING: + 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, + ) -> None: + self._depth -= 1 + if self._depth == 0: + self.close() + pop_context() + + @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._exit_stack.close() + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() + + @property + def command_path(self) -> str: + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = "" + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" + return rv.lstrip() + + def find_root(self) -> Context: + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type: 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) + + if call and callable(value): + return value() + + return value + + return None + + 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)): + value, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) + + ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) + return args + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.deprecated: + 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: a string or list of strings that are environment variables + that should be checked. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + :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, + default: t.Any | t.Callable[[], t.Any] | None = None, + 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}." + ) + + # Skip no default or callable default. + check_default = default if not callable(default) else None + + if check_default is not None: + if multiple: + try: + # Only check the first value against nargs. + check_default = next(_check_iter(check_default), None) + except TypeError: + raise ValueError( + "'default' must be a list when 'multiple' is true." + ) from None + + # Can be None for multiple with empty default. + if nargs != 1 and check_default is not None: + try: + _check_iter(check_default) + except TypeError: + if multiple: + message = ( + "'default' must be a list of lists when 'multiple' is" + " true and 'nargs' != 1." + ) + else: + message = "'default' must be a list when 'nargs' != 1." + + raise ValueError(message) from None + + if nargs > 1 and len(check_default) != nargs: + subject = "item length" if multiple else "length" + raise ValueError( + f"'default' {subject} must match nargs={nargs}." + ) + + 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. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + "default": self.default, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: 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 None: + value = self.default + + if call and callable(value): + value = value() + + return value + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + raise NotImplementedError() + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, t.Any] + ) -> tuple[t.Any, ParameterSource]: + value = opts.get(self.name) # type: ignore + source = ParameterSource.COMMANDLINE + + if value is None: + value = self.value_from_envvar(ctx) + source = ParameterSource.ENVIRONMENT + + if value is None: + value = ctx.lookup_default(self.name) # type: ignore + source = ParameterSource.DEFAULT_MAP + + if value is None: + value = self.get_default(ctx) + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the option's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. + """ + if value is None: + return () if self.multiple or self.nargs == -1 else None + + def check_iter(value: t.Any) -> 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 + + 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: + if value is None: + return True + + if (self.nargs != 1 or self.multiple) and value == (): + return True + + return False + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + value = self.type_cast_value(ctx, value) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + if self.callback is not None: + value = self.callback(ctx, self, value) + + return value + + def resolve_envvar_value(self, ctx: Context) -> str | None: + if self.envvar is None: + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: + for envvar in self.envvar: + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Any | None: + rv: t.Any | None = self.resolve_envvar_value(ctx) + + if rv is not None and self.nargs != 1: + rv = self.type.split_envvar_value(rv) + + return rv + + def handle_parse_result( + self, ctx: Context, opts: cabc.Mapping[str, t.Any], args: list[str] + ) -> tuple[t.Any, list[str]]: + with augment_usage_errors(ctx, param=self): + value, source = self.consume_value(ctx, opts) + + if ( + self.deprecated + and value is not None + 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) + + ctx.set_parameter_source(self.name, source) # type: ignore + + try: + value = self.process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + + value = None + + if self.expose_value: + ctx.params[self.name] = value # type: ignore + + return value, args + + def get_help_record(self, ctx: Context) -> 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 | None = None, + 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) + + default_is_missing = "default" not in attrs + 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 + + # If prompt is enabled but not required, then the option can be + # used as a flag to indicate using prompt or flag_value. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + + if is_flag is None: + if flag_value is not None: + # Implicitly a flag because flag_value was set. + is_flag = True + elif self._flag_needs_value: + # Not a flag, but when used as a flag it shows a prompt. + is_flag = False + else: + # Implicitly a flag because flag options were given. + is_flag = bool(self.secondary_opts) + elif is_flag is False and not self._flag_needs_value: + # Not a flag, and prompt is not enabled, can be used as a + # flag if flag_value is set. + self._flag_needs_value = flag_value is not None + + self.default: t.Any | t.Callable[[], t.Any] + + if is_flag and default_is_missing and not self.required: + if multiple: + self.default = () + else: + self.default = False + + if is_flag and flag_value is None: + flag_value = not self.default + + self.type: types.ParamType + if is_flag and type is None: + # Re-guess the type from the flag value instead of the + # default. + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = is_flag + self.is_bool_flag: bool = is_flag and isinstance(self.type, types.BoolParamType) + self.flag_value: t.Any = flag_value + + # Counting + self.count = count + if count: + if type is None: + self.type = types.IntRange(min=0) + if default_is_missing: + self.default = 0 + + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + if __debug__: + if deprecated and prompt: + raise ValueError("`deprecated` options cannot use `prompt`.") + + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + + if self.prompt and self.is_flag and not self.is_bool_flag: + raise TypeError("'prompt' is not valid for non-boolean flag.") + + if not self.is_bool_flag and self.secondary_opts: + raise TypeError("Secondary flag is not valid for non-boolean flag.") + + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError( + "'prompt' with 'hide_input' is not valid for boolean flag." + ) + + if self.count: + if self.multiple: + raise TypeError("'count' is not valid with 'multiple'.") + + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + flag_value=self.flag_value, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def get_error_hint(self, ctx: Context) -> str: + result = super().get_error_hint(ctx) + if self.show_envvar: + 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 is not None)): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = _split_opt( + (self.opts if 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 + + @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: + # If we're a non boolean flag our default is more complex because + # we need to look at all flags in the same group to figure out + # if we're the default one in which case we return the flag + # value as default. + if self.is_flag and not self.is_bool_flag: + for param in ctx.command.params: + if param.name == self.name and param.default: + return t.cast(Option, param).flag_value + + return None + + return super().get_default(ctx, call=call) + + def prompt_for_value(self, ctx: Context) -> t.Any: + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + assert self.prompt is not None + + # Calculate the default before prompting anything to be stable. + default = self.get_default(ctx) + + # If this is a prompt for a flag we need to handle this + # differently. + if self.is_bool_flag: + return confirm(self.prompt, default) + + # 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, + default=default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + **prompt_kwargs, + ) + + def resolve_envvar_value(self, ctx: Context) -> str | None: + rv = super().resolve_envvar_value(ctx) + + if rv is not None: + if self.is_flag and self.flag_value: + return str(self.flag_value) + 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 | None: + rv: t.Any | None = self.resolve_envvar_value(ctx) + + if rv is None: + return None + + value_depth = (self.nargs != 1) + bool(self.multiple) + + if value_depth > 0: + rv = self.type.split_envvar_value(rv) + + if self.multiple and self.nargs != 1: + rv = batch(rv, self.nargs) + + return rv + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, Parameter] + ) -> tuple[t.Any, ParameterSource]: + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option can be + # given as a flag without a value. This is different from None + # to distinguish from the flag not being given at all. + if value is _flag_needs_value: + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + elif ( + self.multiple + and value is not None + and any(v is _flag_needs_value for v in value) + ): + value = [self.flag_value if v is _flag_needs_value else v for v in value] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt if + # prompting is enabled. + elif ( + source in {None, ParameterSource.DEFAULT} + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the constructor of :class:`Parameter`. + """ + + param_type_name = "argument" + + def __init__( + self, + param_decls: cabc.Sequence[str], + required: bool | None = None, + **attrs: t.Any, + ) -> None: + if required is None: + if attrs.get("default") is not None: + required = False + else: + required = attrs.get("nargs", 1) > 0 + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + if __debug__: + if self.default is not None and self.nargs == -1: + raise TypeError("'default' is not supported for nargs=-1.") + + @property + def human_readable_name(self) -> str: + if self.metavar is not None: + return self.metavar + return self.name.upper() # type: ignore + + def make_metavar(self, 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/.venv/Lib/site-packages/click/decorators.py b/.venv/Lib/site-packages/click/decorators.py new file mode 100644 index 0000000..21f4c34 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/click/exceptions.py b/.venv/Lib/site-packages/click/exceptions.py new file mode 100644 index 0000000..f141a83 --- /dev/null +++ b/.venv/Lib/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: 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: 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: 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/.venv/Lib/site-packages/click/formatting.py b/.venv/Lib/site-packages/click/formatting.py new file mode 100644 index 0000000..9891f88 --- /dev/null +++ b/.venv/Lib/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: + import shutil + + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + width = FORCED_WIDTH + if width is None: + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) + self.width = width + self.current_indent: 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/.venv/Lib/site-packages/click/globals.py b/.venv/Lib/site-packages/click/globals.py new file mode 100644 index 0000000..a2f9172 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/click/parser.py b/.venv/Lib/site-packages/click/parser.py new file mode 100644 index 0000000..a8b7d26 --- /dev/null +++ b/.venv/Lib/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 .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + +# Sentinel value that indicates an option was passed as a flag without a +# value but is not a flag option. Option.consume_value uses this to +# prompt or use the flag_value. +_flag_needs_value = object() + + +def _unpack_args( + args: 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 `None`. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv: list[str | tuple[str | None, ...] | None] = [] + spos: int | None = None + + def _fetch(c: deque[V]) -> V | None: + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return None + + while nargs_spec: + nargs = _fetch(nargs_spec) + + if nargs is None: + continue + + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError("Cannot have two nargs < 0") + + spos = len(rv) + rv.append(None) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1 :] = reversed(rv[spos + 1 :]) + + return tuple(rv), list(args) + + +def _split_opt(opt: str) -> 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, + state: _ParsingState, + ) -> None: + if self.nargs > 1: + assert value is not None + holes = sum(1 for x in value if x is None) + if holes == len(value): + value = None + elif holes != 0: + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + if self.nargs == -1 and self.obj.envvar is not None and value == (): + # Replace empty tuple with None so that a value from the + # environment may be tried. + value = None + + state.opts[self.dest] = value # type: ignore + state.order.append(self.obj) + + +class _ParsingState: + def __init__(self, rargs: 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 = None + + option.process(value, state) + + def _match_short_opt(self, arg: str, state: _ParsingState) -> None: + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = _normalize_opt(f"{prefix}{ch}", self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + value = self._get_value_from_state(opt, option, state) + + else: + value = None + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we 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 + ) -> t.Any: + nargs = option.nargs + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = _flag_needs_value + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = _flag_needs_value + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: _ParsingState) -> None: + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) + else: + long_opt = arg + norm_long_opt = _normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + self._match_short_opt(arg, state) + return + + if not self.ignore_unknown_options: + raise + + state.largs.append(arg) + + +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/.venv/Lib/site-packages/click/py.typed b/.venv/Lib/site-packages/click/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/click/shell_completion.py b/.venv/Lib/site-packages/click/shell_completion.py new file mode 100644 index 0000000..6c39d5e --- /dev/null +++ b/.venv/Lib/site-packages/click/shell_completion.py @@ -0,0 +1,644 @@ +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; +""" + +_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: + return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + if item.help: + return f"{item.type},{item.value}\t{item.help}" + + return f"{item.type},{item.value}" + + +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 + + 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/.venv/Lib/site-packages/click/termui.py b/.venv/Lib/site-packages/click/termui.py new file mode 100644 index 0000000..dcbb222 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/click/testing.py b/.venv/Lib/site-packages/click/testing.py new file mode 100644 index 0000000..7c0e874 --- /dev/null +++ b/.venv/Lib/site-packages/click/testing.py @@ -0,0 +1,565 @@ +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import io +import os +import shlex +import shutil +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) + + +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 __next__(self) -> str: # type: ignore + try: + line = super().__next__() + except StopIteration as e: + raise EOFError() from e + return line + + +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 "") + val = next(text_input).rstrip("\r\n") + 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() + return next(text_input).rstrip("\r\n") + + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: + char = sys.stdin.read(1) + + if echo: + sys.stdout.write(char) + + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi( + stream: t.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: + try: + shutil.rmtree(dt) + except OSError: + pass diff --git a/.venv/Lib/site-packages/click/types.py b/.venv/Lib/site-packages/click/types.py new file mode 100644 index 0000000..684cb3b --- /dev/null +++ b/.venv/Lib/site-packages/click/types.py @@ -0,0 +1,1165 @@ +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" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + if value in {False, True}: + return bool(value) + + norm = value.strip().lower() + + if norm in {"1", "true", "t", "yes", "y", "on"}: + return True + + if norm in {"0", "false", "f", "no", "n", "off"}: + return False + + self.fail( + _("{value!r} is not a valid boolean.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "BOOL" + + +class UUIDParameterType(ParamType): + name = "uuid" + + def convert( + self, value: t.Any, param: 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/.venv/Lib/site-packages/click/utils.py b/.venv/Lib/site-packages/click/utils.py new file mode 100644 index 0000000..ab2fe58 --- /dev/null +++ b/.venv/Lib/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 | 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/.venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA new file mode 100644 index 0000000..a1b5c57 --- /dev/null +++ b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA @@ -0,0 +1,441 @@ +Metadata-Version: 2.1 +Name: colorama +Version: 0.4.6 +Summary: Cross-platform colored terminal text. +Project-URL: Homepage, https://github.com/tartley/colorama +Author-email: Jonathan Hartley +License-File: LICENSE.txt +Keywords: ansi,color,colour,crossplatform,terminal,text,windows,xplatform +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Terminals +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 +Description-Content-Type: text/x-rst + +.. image:: https://img.shields.io/pypi/v/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/pyversions/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Supported Python versions + +.. image:: https://github.com/tartley/colorama/actions/workflows/test.yml/badge.svg + :target: https://github.com/tartley/colorama/actions/workflows/test.yml + :alt: Build Status + +Colorama +======== + +Makes ANSI escape character sequences (for producing colored terminal text and +cursor positioning) work under MS Windows. + +.. |donate| image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif + :target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2MZ9D2GMLYCUJ&item_name=Colorama¤cy_code=USD + :alt: Donate with Paypal + +`PyPI for releases `_ | +`Github for source `_ | +`Colorama for enterprise on Tidelift `_ + +If you find Colorama useful, please |donate| to the authors. Thank you! + +Installation +------------ + +Tested on CPython 2.7, 3.7, 3.8, 3.9 and 3.10 and Pypy 2.7 and 3.8. + +No requirements other than the standard library. + +.. code-block:: bash + + pip install colorama + # or + conda install -c anaconda colorama + +Description +----------- + +ANSI escape character sequences have long been used to produce colored terminal +text and cursor positioning on Unix and Macs. Colorama makes this work on +Windows, too, by wrapping ``stdout``, stripping ANSI sequences it finds (which +would appear as gobbledygook in the output), and converting them into the +appropriate win32 calls to modify the state of the terminal. On other platforms, +Colorama does nothing. + +This has the upshot of providing a simple cross-platform API for printing +colored terminal text from Python, and has the happy side-effect that existing +applications or libraries which use ANSI sequences to produce colored output on +Linux or Macs can now also work on Windows, simply by calling +``colorama.just_fix_windows_console()`` (since v0.4.6) or ``colorama.init()`` +(all versions, but may have other side-effects – see below). + +An alternative approach is to install ``ansi.sys`` on Windows machines, which +provides the same behaviour for all applications running in terminals. Colorama +is intended for situations where that isn't easy (e.g., maybe your app doesn't +have an installer.) + +Demo scripts in the source code repository print some colored text using +ANSI sequences. Compare their output under Gnome-terminal's built in ANSI +handling, versus on Windows Command-Prompt using Colorama: + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/ubuntu-demo.png + :width: 661 + :height: 357 + :alt: ANSI sequences on Ubuntu under gnome-terminal. + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/windows-demo.png + :width: 668 + :height: 325 + :alt: Same ANSI sequences on Windows, using Colorama. + +These screenshots show that, on Windows, Colorama does not support ANSI 'dim +text'; it looks the same as 'normal text'. + +Usage +----- + +Initialisation +.............. + +If the only thing you want from Colorama is to get ANSI escapes to work on +Windows, then run: + +.. code-block:: python + + from colorama import just_fix_windows_console + just_fix_windows_console() + +If you're on a recent version of Windows 10 or better, and your stdout/stderr +are pointing to a Windows console, then this will flip the magic configuration +switch to enable Windows' built-in ANSI support. + +If you're on an older version of Windows, and your stdout/stderr are pointing to +a Windows console, then this will wrap ``sys.stdout`` and/or ``sys.stderr`` in a +magic file object that intercepts ANSI escape sequences and issues the +appropriate Win32 calls to emulate them. + +In all other circumstances, it does nothing whatsoever. Basically the idea is +that this makes Windows act like Unix with respect to ANSI escape handling. + +It's safe to call this function multiple times. It's safe to call this function +on non-Windows platforms, but it won't do anything. It's safe to call this +function when one or both of your stdout/stderr are redirected to a file – it +won't do anything to those streams. + +Alternatively, you can use the older interface with more features (but also more +potential footguns): + +.. code-block:: python + + from colorama import init + init() + +This does the same thing as ``just_fix_windows_console``, except for the +following differences: + +- It's not safe to call ``init`` multiple times; you can end up with multiple + layers of wrapping and broken ANSI support. + +- Colorama will apply a heuristic to guess whether stdout/stderr support ANSI, + and if it thinks they don't, then it will wrap ``sys.stdout`` and + ``sys.stderr`` in a magic file object that strips out ANSI escape sequences + before printing them. This happens on all platforms, and can be convenient if + you want to write your code to emit ANSI escape sequences unconditionally, and + let Colorama decide whether they should actually be output. But note that + Colorama's heuristic is not particularly clever. + +- ``init`` also accepts explicit keyword args to enable/disable various + functionality – see below. + +To stop using Colorama before your program exits, simply call ``deinit()``. +This will restore ``stdout`` and ``stderr`` to their original values, so that +Colorama is disabled. To resume using Colorama again, call ``reinit()``; it is +cheaper than calling ``init()`` again (but does the same thing). + +Most users should depend on ``colorama >= 0.4.6``, and use +``just_fix_windows_console``. The old ``init`` interface will be supported +indefinitely for backwards compatibility, but we don't plan to fix any issues +with it, also for backwards compatibility. + +Colored Output +.............. + +Cross-platform printing of colored text can then be done using Colorama's +constant shorthand for ANSI escape sequences. These are deliberately +rudimentary, see below. + +.. code-block:: python + + from colorama import Fore, Back, Style + print(Fore.RED + 'some red text') + print(Back.GREEN + 'and with a green background') + print(Style.DIM + 'and in dim text') + print(Style.RESET_ALL) + print('back to normal now') + +...or simply by manually printing ANSI sequences from your own code: + +.. code-block:: python + + print('\033[31m' + 'some red text') + print('\033[39m') # and reset to default color + +...or, Colorama can be used in conjunction with existing ANSI libraries +such as the venerable `Termcolor `_ +the fabulous `Blessings `_, +or the incredible `_Rich `_. + +If you wish Colorama's Fore, Back and Style constants were more capable, +then consider using one of the above highly capable libraries to generate +colors, etc, and use Colorama just for its primary purpose: to convert +those ANSI sequences to also work on Windows: + +SIMILARLY, do not send PRs adding the generation of new ANSI types to Colorama. +We are only interested in converting ANSI codes to win32 API calls, not +shortcuts like the above to generate ANSI characters. + +.. code-block:: python + + from colorama import just_fix_windows_console + from termcolor import colored + + # use Colorama to make Termcolor work on Windows too + just_fix_windows_console() + + # then use Termcolor for all colored text output + print(colored('Hello, World!', 'green', 'on_red')) + +Available formatting constants are:: + + Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Style: DIM, NORMAL, BRIGHT, RESET_ALL + +``Style.RESET_ALL`` resets foreground, background, and brightness. Colorama will +perform this reset automatically on program exit. + +These are fairly well supported, but not part of the standard:: + + Fore: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX + Back: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX + +Cursor Positioning +.................. + +ANSI codes to reposition the cursor are supported. See ``demos/demo06.py`` for +an example of how to generate them. + +Init Keyword Args +................. + +``init()`` accepts some ``**kwargs`` to override default behaviour. + +init(autoreset=False): + If you find yourself repeatedly sending reset sequences to turn off color + changes at the end of every print, then ``init(autoreset=True)`` will + automate that: + + .. code-block:: python + + from colorama import init + init(autoreset=True) + print(Fore.RED + 'some red text') + print('automatically back to default color again') + +init(strip=None): + Pass ``True`` or ``False`` to override whether ANSI codes should be + stripped from the output. The default behaviour is to strip if on Windows + or if output is redirected (not a tty). + +init(convert=None): + Pass ``True`` or ``False`` to override whether to convert ANSI codes in the + output into win32 calls. The default behaviour is to convert if on Windows + and output is to a tty (terminal). + +init(wrap=True): + On Windows, Colorama works by replacing ``sys.stdout`` and ``sys.stderr`` + with proxy objects, which override the ``.write()`` method to do their work. + If this wrapping causes you problems, then this can be disabled by passing + ``init(wrap=False)``. The default behaviour is to wrap if ``autoreset`` or + ``strip`` or ``convert`` are True. + + When wrapping is disabled, colored printing on non-Windows platforms will + continue to work as normal. To do cross-platform colored output, you can + use Colorama's ``AnsiToWin32`` proxy directly: + + .. code-block:: python + + import sys + from colorama import init, AnsiToWin32 + init(wrap=False) + stream = AnsiToWin32(sys.stderr).stream + + # Python 2 + print >>stream, Fore.BLUE + 'blue text on stderr' + + # Python 3 + print(Fore.BLUE + 'blue text on stderr', file=stream) + +Recognised ANSI Sequences +......................... + +ANSI sequences generally take the form:: + + ESC [ ; ... + +Where ```` is an integer, and ```` is a single letter. Zero or +more params are passed to a ````. If no params are passed, it is +generally synonymous with passing a single zero. No spaces exist in the +sequence; they have been inserted here simply to read more easily. + +The only ANSI sequences that Colorama converts into win32 calls are:: + + ESC [ 0 m # reset all (colors and brightness) + ESC [ 1 m # bright + ESC [ 2 m # dim (looks same as normal brightness) + ESC [ 22 m # normal brightness + + # FOREGROUND: + ESC [ 30 m # black + ESC [ 31 m # red + ESC [ 32 m # green + ESC [ 33 m # yellow + ESC [ 34 m # blue + ESC [ 35 m # magenta + ESC [ 36 m # cyan + ESC [ 37 m # white + ESC [ 39 m # reset + + # BACKGROUND + ESC [ 40 m # black + ESC [ 41 m # red + ESC [ 42 m # green + ESC [ 43 m # yellow + ESC [ 44 m # blue + ESC [ 45 m # magenta + ESC [ 46 m # cyan + ESC [ 47 m # white + ESC [ 49 m # reset + + # cursor positioning + ESC [ y;x H # position cursor at x across, y down + ESC [ y;x f # position cursor at x across, y down + ESC [ n A # move cursor n lines up + ESC [ n B # move cursor n lines down + ESC [ n C # move cursor n characters forward + ESC [ n D # move cursor n characters backward + + # clear the screen + ESC [ mode J # clear the screen + + # clear the line + ESC [ mode K # clear the line + +Multiple numeric params to the ``'m'`` command can be combined into a single +sequence:: + + ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background + +All other ANSI sequences of the form ``ESC [ ; ... `` +are silently stripped from the output on Windows. + +Any other form of ANSI sequence, such as single-character codes or alternative +initial characters, are not recognised or stripped. It would be cool to add +them though. Let me know if it would be useful for you, via the Issues on +GitHub. + +Status & Known Problems +----------------------- + +I've personally only tested it on Windows XP (CMD, Console2), Ubuntu +(gnome-terminal, xterm), and OS X. + +Some valid ANSI sequences aren't recognised. + +If you're hacking on the code, see `README-hacking.md`_. ESPECIALLY, see the +explanation there of why we do not want PRs that allow Colorama to generate new +types of ANSI codes. + +See outstanding issues and wish-list: +https://github.com/tartley/colorama/issues + +If anything doesn't work for you, or doesn't do what you expected or hoped for, +I'd love to hear about it on that issues list, would be delighted by patches, +and would be happy to grant commit access to anyone who submits a working patch +or two. + +.. _README-hacking.md: README-hacking.md + +License +------- + +Copyright Jonathan Hartley & Arnon Yaari, 2013-2020. BSD 3-Clause license; see +LICENSE file. + +Professional support +-------------------- + +.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png + :alt: Tidelift + :target: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + +.. list-table:: + :widths: 10 100 + + * - |tideliftlogo| + - Professional support for colorama is available as part of the + `Tidelift Subscription`_. + Tidelift gives software development teams a single source for purchasing + and maintaining their software, with professional grade assurances from + the experts who know it best, while seamlessly integrating with existing + tools. + +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + +Thanks +------ + +See the CHANGELOG for more thanks! + +* Marc Schlaich (schlamar) for a ``setup.py`` fix for Python2.5. +* Marc Abramowitz, reported & fixed a crash on exit with closed ``stdout``, + providing a solution to issue #7's setuptools/distutils debate, + and other fixes. +* User 'eryksun', for guidance on correctly instantiating ``ctypes.windll``. +* Matthew McCormick for politely pointing out a longstanding crash on non-Win. +* Ben Hoyt, for a magnificent fix under 64-bit Windows. +* Jesse at Empty Square for submitting a fix for examples in the README. +* User 'jamessp', an observant documentation fix for cursor positioning. +* User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7 + fix. +* Julien Stuyck, for wisely suggesting Python3 compatible updates to README. +* Daniel Griffith for multiple fabulous patches. +* Oscar Lesta for a valuable fix to stop ANSI chars being sent to non-tty + output. +* Roger Binns, for many suggestions, valuable feedback, & bug reports. +* Tim Golden for thought and much appreciated feedback on the initial idea. +* User 'Zearin' for updates to the README file. +* John Szakmeister for adding support for light colors +* Charles Merriam for adding documentation to demos +* Jurko for a fix on 64-bit Windows CPython2.5 w/o ctypes +* Florian Bruhin for a fix when stdout or stderr are None +* Thomas Weininger for fixing ValueError on Windows +* Remi Rampin for better Github integration and fixes to the README file +* Simeon Visser for closing a file handle using 'with' and updating classifiers + to include Python 3.3 and 3.4 +* Andy Neff for fixing RESET of LIGHT_EX colors. +* Jonathan Hartley for the initial idea and implementation. diff --git a/.venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD new file mode 100644 index 0000000..cd6b130 --- /dev/null +++ b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD @@ -0,0 +1,31 @@ +colorama-0.4.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +colorama-0.4.6.dist-info/METADATA,sha256=e67SnrUMOym9sz_4TjF3vxvAV4T3aF7NyqRHHH3YEMw,17158 +colorama-0.4.6.dist-info/RECORD,, +colorama-0.4.6.dist-info/WHEEL,sha256=cdcF4Fbd0FPtw2EMIOwH-3rSOTUdTCeOSXRMD1iLUb8,105 +colorama-0.4.6.dist-info/licenses/LICENSE.txt,sha256=ysNcAmhuXQSlpxQL-zs25zrtSWZW6JEQLkKIhteTAxg,1491 +colorama/__init__.py,sha256=wePQA4U20tKgYARySLEC047ucNX-g8pRLpYBuiHlLb8,266 +colorama/__pycache__/__init__.cpython-312.pyc,, +colorama/__pycache__/ansi.cpython-312.pyc,, +colorama/__pycache__/ansitowin32.cpython-312.pyc,, +colorama/__pycache__/initialise.cpython-312.pyc,, +colorama/__pycache__/win32.cpython-312.pyc,, +colorama/__pycache__/winterm.cpython-312.pyc,, +colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522 +colorama/ansitowin32.py,sha256=vPNYa3OZbxjbuFyaVo0Tmhmy1FZ1lKMWCnT7odXpItk,11128 +colorama/initialise.py,sha256=-hIny86ClXo39ixh5iSCfUIa2f_h_bgKRDW7gqs-KLU,3325 +colorama/tests/__init__.py,sha256=MkgPAEzGQd-Rq0w0PZXSX2LadRWhUECcisJY8lSrm4Q,75 +colorama/tests/__pycache__/__init__.cpython-312.pyc,, +colorama/tests/__pycache__/ansi_test.cpython-312.pyc,, +colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc,, +colorama/tests/__pycache__/initialise_test.cpython-312.pyc,, +colorama/tests/__pycache__/isatty_test.cpython-312.pyc,, +colorama/tests/__pycache__/utils.cpython-312.pyc,, +colorama/tests/__pycache__/winterm_test.cpython-312.pyc,, +colorama/tests/ansi_test.py,sha256=FeViDrUINIZcr505PAxvU4AjXz1asEiALs9GXMhwRaE,2839 +colorama/tests/ansitowin32_test.py,sha256=RN7AIhMJ5EqDsYaCjVo-o4u8JzDD4ukJbmevWKS70rY,10678 +colorama/tests/initialise_test.py,sha256=BbPy-XfyHwJ6zKozuQOvNvQZzsx9vdb_0bYXn7hsBTc,6741 +colorama/tests/isatty_test.py,sha256=Pg26LRpv0yQDB5Ac-sxgVXG7hsA1NYvapFgApZfYzZg,1866 +colorama/tests/utils.py,sha256=1IIRylG39z5-dzq09R_ngufxyPZxgldNbrxKxUGwGKE,1079 +colorama/tests/winterm_test.py,sha256=qoWFPEjym5gm2RuMwpf3pOis3a5r_PJZFCzK254JL8A,3709 +colorama/win32.py,sha256=YQOKwMTwtGBbsY4dL5HYTvwTeP9wIQra5MvPNddpxZs,6181 +colorama/winterm.py,sha256=XCQFDHjPi6AHYNdZwy0tA02H-Jh48Jp-HvCjeLeLp3U,7134 diff --git a/.venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL new file mode 100644 index 0000000..d79189f --- /dev/null +++ b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.11.1 +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any diff --git a/.venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..3105888 --- /dev/null +++ b/.venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2010 Jonathan Hartley +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holders, nor those of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/Lib/site-packages/colorama/__init__.py b/.venv/Lib/site-packages/colorama/__init__.py new file mode 100644 index 0000000..383101c --- /dev/null +++ b/.venv/Lib/site-packages/colorama/__init__.py @@ -0,0 +1,7 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from .initialise import init, deinit, reinit, colorama_text, just_fix_windows_console +from .ansi import Fore, Back, Style, Cursor +from .ansitowin32 import AnsiToWin32 + +__version__ = '0.4.6' + diff --git a/.venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c14775cacbc958702c3a9150d51be9e079e56bc GIT binary patch literal 505 zcmXv~O^XyU5KX4jJs&-@2T{=59K1A(qwI=@MTB*iVFi(eaS%cv#O|n>NjgK4nceZ` zukarT{uYnoB_IkO#G9};PgY#F4zFI-BY9QzX*3!jh!6F_n*m1XM=GwO^PgOt0C|rZ z)L@Marr307$7D1!gc6g}94D=#3)7`t)1y7pr+qV^@FDawU78^s0w;8|&8y-U_@F9} zM71i!qLG)`UFK13wROS>9^`q5Hy3Lk_$zhJ&s5vkGtX<=daGqcBAHqzBY7<9Wz1(` zt7R;X*3Mg3;aDEFUOl%jRJ*_THorHyKY8%1+KGc?ri4~rx(w!UBtf2_NYDj{M8I3u zOLad%I*}`rQYIx>m}P&@4gYmG-$>_`Z8`slE}(_}=%WKR2LsP$!mwlMm%*;sOf_g+<+!LzCG;qdgyc;m-YE&P(5z#7txkL|*;$wsyt_Ef!O;3n^^KoYN|XIHnm%X!*^ x^T}!(dk>9m)>=Mt*TFm;*WUv8onefBqWKpz|AuxyqusCQ_V=9|?@D(K)W5NUkedJi literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-312.pyc b/.venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fad3ba46855e48f3b2bb04642ad94b24e4eb7ac2 GIT binary patch literal 3958 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-312.pyc b/.venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b13890cff9ee783c91309b9498ebc554a8b51cb7 GIT binary patch literal 3563 zcmcgvU2Gf25#GH!@{T-`wtnb{vP{#8(uPXpptg&)LDRrUqBwC%1dyD-1R?gUJ4;7V zJYsicN(Kv1hSAu7oj6EPrGU};Aqea`1q}41k9{oA7s)>mdu9O#ae?MdkzBwCTA;Ig zyd&ibDf-eSc6N4m_I7q>zM18pu&H0 zIvWyH*~EqdH55TCF64of50b|$#;YTTA-&9>-BmFjUXapbP`5uXi+|iYB($9 z#vqRon?Q?cCSX?qbwYj(wSYP2}* zRH#$7+^{F(Mi(l?nz1Rfh#MgmwOG+8m)*os#)y4tfmyE+;*c9?i|hMM<&_r;C#Xee zVZxX%ylT-o=2Qz4wrOSFCQiwkX0$MFP_V0Fn&gWgrEXjeeJicy9GgWAQ@ z4k;n9NaO4bMS3f5CiI! zEVPsjW@<8GEiw6NCUOh z-c>c-()vC=en-o!Yolx0=soTE4H;?s#nNg1O~0tX2=&i_vMZ=A(^N<8I;!KkG$Z>; z6Pc)XD%vPnT_gQl@6J*^E(H$~bHm5M+>rxn0c<1H!?wiW@N&m?I*B ztAa{rEW+FnthKAI!oNeg17x5Gw{#VbTrvz}&p@Tufi%z;`mT>(`6PAi_3I~A2M*uu zKhjDJtfpSSlQ_1j9eWf-eS20$KOAiwZ}lcU=AjyW@9gr~RW;R2?Yo$G6h`rZtNOKf zuUofH{!zITdu>C;k#T%Uz7)MO^*;&!LqoBrT3X_RT^DyKh>#^$-FI z2ZcjdAS4gWHh}NyfV~6oVgPj(`111Z4&NmipoUwXB;oVfkcagY@PxaGXRy7T+YWs< zY+B-$M_}$KRC)kNfc|8LUrRd_@XqCER{#LXpl~I{@tzd{pmU^BaF9S2lJ?vWKoz`% zI)D3u4<8!8A(Q>^d6MCTN8rh9gmC-0TD5F2$WQZx z-+@4#2Q+>}&$?nIw{BK)qZO;>XK|kg$Wm?wA6NsW1Ve11$ld>)m&C;y3wUVe(HW?)FSH z-fXHp>uTyYpf0&zI8o2gf0cPKlvz_!|A`$QA76mFH3j52ymtUU;AUQ_BOCI|r}%x0 zA#t1Nm(mJwoea~R4&jv)MDxzL|4RCuT*DrIBoTGU2znT#m4Xl zN*aBnkKtd(M&;kiV|W_IgYtil9o$QW?|I%OqWX@pgLtvydmujxHHaPDJ6g%7z1R_S zyx1}9nKwvzur7!lKZ6cD9u)Fiw|PbClt0G-rbG2mDKH}}X&2w1BnI=VZ(dZ@h7YIh8yM__gVpDH_=`@Z&ct( zOoV?DZY`nblc_-a@w{U$l&znTAAtzR7kvi^q(_XuMDhEm=RS)54UKGu`*D2ppoGV^ zqA9$0^M?vPu$f5W0~-gC6kErGYk2T$DTI5rWF+m~;N0eJ1&?ed!npVAeuVemLm6?> Fe*;WxwgvzI literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/__pycache__/win32.cpython-312.pyc b/.venv/Lib/site-packages/colorama/__pycache__/win32.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..086bab8d973cac9795693a930ae7414efd5ddbb0 GIT binary patch literal 8148 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 Z=N|+TYE~PrH={3`ZPzdAzq~*}`G0-?9~=Mx literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-312.pyc b/.venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7094c60fd6973ba35c759edf63d2e54e161763e GIT binary patch literal 9101 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 e+qLCj`L3r6%$&VT2vep zoR}Nql3JWyl3x%LoSBlU5Kxq#m6}{q9OIT)T;dqusZds|;FgnEoE_tln4Fzjq~MaD zl^>&5mYP=<bQ=D0ns#^e*N=#2Jj!DkX$uCOGP1G+*EiNh6kB`sH%PfhH*DI*J g#bJ}1pHiBWYFESxbOIv~7lRldnHd=wiEQT!Ws2lukWxH65Haf@%QJr^*>e3wRBKJp| zw0){F`!*p|aS%;hFLW13PRI%Vx@Txd{0>u@)O2Gop=+j;NhIf?6u_FslJ&)K&FI(v2z?M%dmi`ppvLV74DL#s5(GOst8h6 zC6G>42INj?fY9F9EeQ;UXM*AGO-~|pEa`cv5BOr`L5DzN}DnC*lvaT6Z^NUTzt?6cV6HG`y4PuFGIZ0jPW5t_Sybp%HY5A`G>-Jwd zI=`RYPzIhT4HgZX%D@gFN!2FfCa_I@4=T&V0LhC{*d9XSrJ7ON*oxNDw&w_K1gu{+ z-Taxxvov{=?gt-j2Ep9$bh-Ngx?4c-hfG_shhk_6I+VRLx7b`-chQAkqmIFr$ZxH_ z`{Mn2roODFhrn62D4Y^o)DYBO_~w?Dl`1U(aD}1@|N4HG@ukO7L=*Vg*ua;*i3+@E z18?zFsFDqQLwVd{O_gon@vW>Ws?!D@ceSRdE*p4!muiZt*uY!-=+!zK_*ZHM2UD%L zfwv$O)olacZZUp?4Sa_MzR?E$H4A)`4ZP0+zt0B#4Ga8!8~CFZ_~tS`NBjpOi?Vn6 z)X5A}y3k_Z}#qscvDUXbY!x7VYJv=scOW0sW4jmm{l06+>Aq_#bdFd z!QAz)KY&b!sZGc*X!6Dan2X+uzkY3_2Uf1%0!CgUjNjVwO51~j58GGTSEC!s$x2__ zO55tdhSFW>v+Czv@vf#el-|mIo)ypP`x{D6rLSY9V|8&u>8tcv^|KnI=qu1QpY>zU ziM;2;8qE7lMa;(@f8OI?gPEVM@>=)p%zHZ5VD`bPzQ^;P<7+Vg{wi-*-qW=PE8v+y zaSqmf`DmNUnLS?JGMCH72k5_lUKUTaN;KUf<#2Dbj7~sSh;KUf< z#2Dbj7~s4b0A6)^r+ZHVo@ofcG(=z;LNE<6n1&!sLlmZwU+JqbNs1{v#XN?-j>3oH zFbd2q=n)i{CD3Ci-bC>h3OL#m>PLZxZh8VmCyFi<-6(3$IE?r3VE;nit8VOpApbMo zo33Ro#(QULS&K2zuD*=-N~hc^F1u9Ar;PVL*-*|_u_5EVU48jzn@aE{y!RvQ#ee@i z3HhuPS(Lof{6p&l1x9biFY|8!kE5nIGce88C&ba-B}m+yn83rZj=k@aZAp^PZZ|mO!`t;O za&Wt|MIPAc6y-jSLkLa)uk7M+|9*DWVJZuANdun?#VZIZVJ9g8S3CS4g9$ukPuPu4 zBYOug^4RqgyCtER=Tde%g2y^OuYRlzpuoG1!d=NZzTETlUGU*el|BMuOB4j*DXIU1 z9QlhJ&6A_QlVg82HGh@*BDLgxQmj08o)h}+ef*q&+WJf&Qp3H)pGD8FlJfcRmPn*_ GHqbxq0Exx` literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc b/.venv/Lib/site-packages/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd3f521ba5fb833313c0ef0275bd36b11607c55e GIT binary patch literal 18019 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 DY$o`& literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/tests/__pycache__/isatty_test.cpython-312.pyc b/.venv/Lib/site-packages/colorama/tests/__pycache__/isatty_test.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1abd7a0270f979400e047ecc229fb7dcc359f30b GIT binary patch literal 4794 zcmd^D-ER|D7Qf^1*kgMfI|-r=8K+JI1QSRCDbR+rl|x8j+X7NjwRSC8)66V5@kjQ~ zBy1vD^?{Xw=!&jZE0CI1Qx&z15~=$KRF#PO)V|=_$aI8+w0&6dhLTE1dFnYc9)IBk zcDqmQm2&RA=iWJU=lst3-4lQB^|}eP&nE^i{J}}cPdKp}R~l@*4}p-&7T<#>b>|d_+;FwCr?zEGa9Y zb2K}tOlWF+R8qCbxznLpH8h%%)T#I>X<|yDp^@xlHr_j{WM<>9C*O^$NliI84NKB_ zMU78nQ&}pdr9KV(srH$#PvE3?dQRt)YC=lDfoLln!v%k80K^C6o+of)=hBY6XK&GS zFy}d#_w?Pi_iex$nLuGR%Nr%{C?v+nBxaKt2mn^LfwapkNKUqcbjTb?r|ba98$sfl z2_5~ z{ZVHD4;2F|6rwG-okzG55Sa6=6-V`2$|l&Nz5oJHYkKDD)B<~#7q0oP_!jx4){ngf ze(yc+j=x6l3jS+nubf>xwKVqe8wFwiBbGEhb1ix$y1C(jhreo%w6eEaxrpfetwZ2N zpL$FPMx_)Wfx%zGfbPOoXC|A$w5Wl>V|W}^5Q&}jNSOK^@U;3e2mqs{uHIu}@=pvA-Z3DyL7i@)2j{kcxSI zHPRf}!`|A%MS5$K2>Hg~vF~8e2)y?gt$c%2b5bK7D*;{RFfdta8q$c-8nIrz^Xuo2 zbJvG0CUg6q=!9qgAw=5PPunA2?rR=Kx4c|rr|VXW6US{_ z3}JX12y3_QM$<2WC=;cH;;}n{9)Pg{Ikp`@ z0E>a1+kqYvOWFe#YIqV;z(}?C;o5JMYM4HiWP=i}R2~@ma@kO;htgo%bgS&PtQp+7 z?K2wQx<<1S-g?&R4I79}U8|(IhDC+Zwzg59=1-b8pn+YDY2d?-#`@Zlhp&n({A5}Q zFC*>@a!2;0-5{!2Tx<|*N;%ZJw-2@Q#xPo)1yN%6d(G`PM(#9s7P^l9OS{uGbl1P5 z=nv)mp__p(cYoGV@b_0~Ru8Q7<~v_3_y<9G-S;=&O@6g)r9B@yQt%&rZg;NPco?s_c~GylbHlqm zYau6Uy1C(g*V+Lm>Ib;tp)F`=KU9ZX#rBiR*vE@sE8ZH_1e4_`h0iFp`dHgCPRrS} zb)5dHcb2bA>tuvfE=I8qQ!R&IrNR>hT$eDH;0@ReNfm11x-7aYw8PZfmIRsrzZD*Kgt*_{^$ zisE2S94v~FoEXWACkn#I)9iWVsx9qK92s%3-@3Sw7U!9;TjvvrjFeUq3EiDYq_gr& z3dces@uwLnWv);Wz4sgn__>47{U{Ei=tFS`#4@268%EJecj5>IlA((Jzf9KccCKe# zuyg(E0?Qp_4 z4_Ss`ej@Jsr0YNA`TJ!24jEr}Fiii?jxckQ`6%!+!T#af46zFf^FOd{Kic`fj6P(E IeV1YV7tI)c_5c6? literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/tests/__pycache__/utils.cpython-312.pyc b/.venv/Lib/site-packages/colorama/tests/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b3606bff023d8b8c556f04c3107ea09b76eada2 GIT binary patch literal 2483 zcmds3-A^1<6u)<7KW3MOQkE5iNSDxxi@F4jVz8QQL7-H(MDT&lCgZR((uLU_ymx?Z z0|}BPrqQJIMf?j)2%7lji!VI;LK7RwfQgAU(YI}uq|*4{IWw~#P-5zzic-`CUC4fV%BAfrU}6p8B9#2wL@BjvMEAE5p^)UQ!hDv}*{p_ByeKwUck?ON`nbjB|(xl0<`Pr-2lNomfyP|HR+ycrL(MXD_BqgY{NK#@! zh>2DJ25L%fYHB)DF|JI24wZo^7+9b&Avv{8wyEXen*K3I+vsCsXfXXD*BDPvD!KHq z#^+6AAw8K@wde#h=Cq8-(<2Hu6BAdXOFTNFD||jZrex+d79BR`jC6cS%P*zh%g&^E z*3@2EfG%)|r!$6bFeRt-nHo2F-=dk-d3>Q@`LbLw&BCqb>aODs5`g`1JjFG#e=b}G zg(SBwX2 z-T#s2kh9~Yk$)PhN1P4WUfEPT+rq8h3fIr%x{o`ry6?N}Hw6gX9#V-d$O z>^Tt!0s!6cl|KS;jZlaM6EvsHVehH3N>}PE9j&?<7>SI@~yZ`w`npa zSc1VV51vg-V9&zrmd9Y(S&nN(qc#tg&){gl+5u~Zk4HiLLH=4J4;mWp4BQ@A8{Thf zDPAd#9(V$_wlR9lF_hO|j?=Rp{04fT&cT`kb2#OxvMQWSzzMWU+`rSZBGlHBt79Cp z*fX#&B(TT}-12c#H5N^~))?CsQNz5p8jw8M?XEV!hGTk1D~tx2wxBB+O`a(*JnuI6 z_y7op)t1Os@7>;0#z!K#@Yc_Pjt5fkPVjbcFWh}ky7`i^|S-H+v!d81a`aqi1d0BulaZT{D8+oR?z(epl4?o#+xN%4ThJI9iO(- z&KqXm_jpa#ytdss^)6V_W#@H#hxLOo9yl+6D2bHP-^rz4NZSDk9+1G1PoS-v(h-5` zND^pd^G(zvC5Z^l4+RemmqpM@sFgxC=BLlQ7-*#y7wf8GT`uOOmu&2z0Gj B-e3R# literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/tests/__pycache__/winterm_test.cpython-312.pyc b/.venv/Lib/site-packages/colorama/tests/__pycache__/winterm_test.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7a96cc7989c3f947953bded35f0e423864ff02f GIT binary patch literal 6625 zcmeHMO>7&-6`uVexg?h~YwO>xOeHpK29adTsqMx=4Ox;E6}IGBG+Yw|78G}7k>Zas zyOJV826}J{2{m9DIk-jx)V(wj9H1^xpat4X&%KD10;&raNfBSvlTO_o%O^#43T-+mGNc$F@IK!iP=Cbzz~-76WM*2$R34# z!j-wlg319|7(Yn}6|6Al<*ED&TyG5p%h<6QF z4MsR!h$^bqlTa0%&n8ki-KUPHCT`?1imFCfoxhq-j_a<8gq9pzV_^xdxS7iJFRIXNvLcL8S;z5v29HA_F-9ga7KN6XC(wsQX73X7C&YLdb1CjL9Bge# zf1|bs)^ZjX{s~G4MRwWvd29B9qqO$&?ZaLk8$bU(@DptO#C_oBwebtooQ2Oe7OWZb z+r|#eh>?KquP7MNQfNq``V-ki$8;*Ef!eV?)0M2Kz|aE^6cM`#Ubslx7;@O9xd@mY`j-d$F=-Kd^ja5 z;cGOXR+5?;znoCD?rQ_#DK&gKlTgRwZ^Ld14fp2L`FO{alADTONsYwSl%}*#z)<40 zqQ*_wbi!}c)XoZcB>d`_n9&7n4CGQ;p$;TOcM`=Bv-beVr;Y&m0eOC?@x8Ni!s^lX zIblUOvier-S#@n~V;7%WMHc zQUQ+DChbA53SGEFC zk$pH!kK(w9<_Yc!9f?|Ksx@451h-ZH0tAe=Y@eUjH_Y+(e5;kJp%K3Pb^~pF)Vkap z`SA3L(Ehysn-8T2k}=`_XbM3Fkev|)Iz9{y64ovzP z+6KSSISl?5+=ior*q1Q4J-p6xl9_)&js;)ns*GZMVGyc*VIVRJ1kOWdyIPyh#t47) z)qb1o#R)8rqy~hJ}|{{)zE}S@Tnl)56%Z+ z7+NnENmqoEHiIrTM~cmnQuC=|^QouDJ{HcEt;HTrF$FQg>EEE?Xf2FpIb;)+_K;E& zP#LFy3Ta?v&mGe_CT&>@2vR9uB7JmBU>#FYn{CXHMZV&)(PEZ#<=%PkLfbuY51s#CmvE4k zV!s~D5c&pmZ&xA4UY*C3d`}f#f=Sy|h`m>V2|bUb4ak;6^ncWHBBte_4|lK}OgnU! zaRu7#+`X8@7%v%x`q~V}=>9(NQFk()a_WM{(0i3uyy{^#I6HQ%tvOI8VHj)fBj|I0WakmBUu2!%U}~n@eN1T2stjN7)n^ zwcX<(IJ0Hvxmz4|!%a?xKx=qYBl&F{wrrD7Kfx zb4BsoYOt{sY%K;`AM+mse;!;8pIQo>e%iGpp8ed#d41b+QVU$KeC@y`vmaS+dS4rW zReVe|!sy<74jxowh0pLQK@6g;#b)mN^H( zrA`BRS%f`)V@YU&jP0wyY%mhi1xd;!vWg_>K1s^v<;e`TMM=WfsqzTL+XoFJ!Hh@e z)EQM5CNc>Pa-1wYL2C)BsnaQKjJ}4Gx{#bhaskOjBz;H*fGn1yp^+(5^j8MKhS~!7 zzDxe<@-6vaU3MMYU_GwLCP&=I*Ri#6khnt&+(Yq!SPGpehE9CwS`KwAow~H_>e*lq zx%wHL+smvQ?Ty!n+i&s-xz1PkKx^aJZhVCDRAd9qjk^EbTuVC9<*P^st&Jl#xy_Lc z7Fvd5Fb)fKhL8V}6bVsGGIU`Qp5YLT6w_^r*+p4Z0dt@OUk}SSAJnGqAIMJR;SG{f z_+x;_LEQsh7he{Q=LO?6p`6$nO8PFcyoRI=iSd>oyj8}_cj!&%1LYC*7Law0VVKW| z?-SBlB%SL#$FzOINB$uYrmIL!zi?BA@hzNuL9kv=bA%V?3V-67|I7i!Za?=29|QE?02qrs literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/colorama/tests/ansi_test.py b/.venv/Lib/site-packages/colorama/tests/ansi_test.py new file mode 100644 index 0000000..0a20c80 --- /dev/null +++ b/.venv/Lib/site-packages/colorama/tests/ansi_test.py @@ -0,0 +1,76 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import sys +from unittest import TestCase, main + +from ..ansi import Back, Fore, Style +from ..ansitowin32 import AnsiToWin32 + +stdout_orig = sys.stdout +stderr_orig = sys.stderr + + +class AnsiTest(TestCase): + + def setUp(self): + # sanity check: stdout should be a file or StringIO object. + # It will only be AnsiToWin32 if init() has previously wrapped it + self.assertNotEqual(type(sys.stdout), AnsiToWin32) + self.assertNotEqual(type(sys.stderr), AnsiToWin32) + + def tearDown(self): + sys.stdout = stdout_orig + sys.stderr = stderr_orig + + + def testForeAttributes(self): + self.assertEqual(Fore.BLACK, '\033[30m') + self.assertEqual(Fore.RED, '\033[31m') + self.assertEqual(Fore.GREEN, '\033[32m') + self.assertEqual(Fore.YELLOW, '\033[33m') + self.assertEqual(Fore.BLUE, '\033[34m') + self.assertEqual(Fore.MAGENTA, '\033[35m') + self.assertEqual(Fore.CYAN, '\033[36m') + self.assertEqual(Fore.WHITE, '\033[37m') + self.assertEqual(Fore.RESET, '\033[39m') + + # Check the light, extended versions. + self.assertEqual(Fore.LIGHTBLACK_EX, '\033[90m') + self.assertEqual(Fore.LIGHTRED_EX, '\033[91m') + self.assertEqual(Fore.LIGHTGREEN_EX, '\033[92m') + self.assertEqual(Fore.LIGHTYELLOW_EX, '\033[93m') + self.assertEqual(Fore.LIGHTBLUE_EX, '\033[94m') + self.assertEqual(Fore.LIGHTMAGENTA_EX, '\033[95m') + self.assertEqual(Fore.LIGHTCYAN_EX, '\033[96m') + self.assertEqual(Fore.LIGHTWHITE_EX, '\033[97m') + + + def testBackAttributes(self): + self.assertEqual(Back.BLACK, '\033[40m') + self.assertEqual(Back.RED, '\033[41m') + self.assertEqual(Back.GREEN, '\033[42m') + self.assertEqual(Back.YELLOW, '\033[43m') + self.assertEqual(Back.BLUE, '\033[44m') + self.assertEqual(Back.MAGENTA, '\033[45m') + self.assertEqual(Back.CYAN, '\033[46m') + self.assertEqual(Back.WHITE, '\033[47m') + self.assertEqual(Back.RESET, '\033[49m') + + # Check the light, extended versions. + self.assertEqual(Back.LIGHTBLACK_EX, '\033[100m') + self.assertEqual(Back.LIGHTRED_EX, '\033[101m') + self.assertEqual(Back.LIGHTGREEN_EX, '\033[102m') + self.assertEqual(Back.LIGHTYELLOW_EX, '\033[103m') + self.assertEqual(Back.LIGHTBLUE_EX, '\033[104m') + self.assertEqual(Back.LIGHTMAGENTA_EX, '\033[105m') + self.assertEqual(Back.LIGHTCYAN_EX, '\033[106m') + self.assertEqual(Back.LIGHTWHITE_EX, '\033[107m') + + + def testStyleAttributes(self): + self.assertEqual(Style.DIM, '\033[2m') + self.assertEqual(Style.NORMAL, '\033[22m') + self.assertEqual(Style.BRIGHT, '\033[1m') + + +if __name__ == '__main__': + main() diff --git a/.venv/Lib/site-packages/colorama/tests/ansitowin32_test.py b/.venv/Lib/site-packages/colorama/tests/ansitowin32_test.py new file mode 100644 index 0000000..91ca551 --- /dev/null +++ b/.venv/Lib/site-packages/colorama/tests/ansitowin32_test.py @@ -0,0 +1,294 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from io import StringIO, TextIOWrapper +from unittest import TestCase, main +try: + from contextlib import ExitStack +except ImportError: + # python 2 + from contextlib2 import ExitStack + +try: + from unittest.mock import MagicMock, Mock, patch +except ImportError: + from mock import MagicMock, Mock, patch + +from ..ansitowin32 import AnsiToWin32, StreamWrapper +from ..win32 import ENABLE_VIRTUAL_TERMINAL_PROCESSING +from .utils import osname + + +class StreamWrapperTest(TestCase): + + def testIsAProxy(self): + mockStream = Mock() + wrapper = StreamWrapper(mockStream, None) + self.assertTrue( wrapper.random_attr is mockStream.random_attr ) + + def testDelegatesWrite(self): + mockStream = Mock() + mockConverter = Mock() + wrapper = StreamWrapper(mockStream, mockConverter) + wrapper.write('hello') + self.assertTrue(mockConverter.write.call_args, (('hello',), {})) + + def testDelegatesContext(self): + mockConverter = Mock() + s = StringIO() + with StreamWrapper(s, mockConverter) as fp: + fp.write(u'hello') + self.assertTrue(s.closed) + + def testProxyNoContextManager(self): + mockStream = MagicMock() + mockStream.__enter__.side_effect = AttributeError() + mockConverter = Mock() + with self.assertRaises(AttributeError) as excinfo: + with StreamWrapper(mockStream, mockConverter) as wrapper: + wrapper.write('hello') + + def test_closed_shouldnt_raise_on_closed_stream(self): + stream = StringIO() + stream.close() + wrapper = StreamWrapper(stream, None) + self.assertEqual(wrapper.closed, True) + + def test_closed_shouldnt_raise_on_detached_stream(self): + stream = TextIOWrapper(StringIO()) + stream.detach() + wrapper = StreamWrapper(stream, None) + self.assertEqual(wrapper.closed, True) + +class AnsiToWin32Test(TestCase): + + def testInit(self): + mockStdout = Mock() + auto = Mock() + stream = AnsiToWin32(mockStdout, autoreset=auto) + self.assertEqual(stream.wrapped, mockStdout) + self.assertEqual(stream.autoreset, auto) + + @patch('colorama.ansitowin32.winterm', None) + @patch('colorama.ansitowin32.winapi_test', lambda *_: True) + def testStripIsTrueOnWindows(self): + with osname('nt'): + mockStdout = Mock() + stream = AnsiToWin32(mockStdout) + self.assertTrue(stream.strip) + + def testStripIsFalseOffWindows(self): + with osname('posix'): + mockStdout = Mock(closed=False) + stream = AnsiToWin32(mockStdout) + self.assertFalse(stream.strip) + + def testWriteStripsAnsi(self): + mockStdout = Mock() + stream = AnsiToWin32(mockStdout) + stream.wrapped = Mock() + stream.write_and_convert = Mock() + stream.strip = True + + stream.write('abc') + + self.assertFalse(stream.wrapped.write.called) + self.assertEqual(stream.write_and_convert.call_args, (('abc',), {})) + + def testWriteDoesNotStripAnsi(self): + mockStdout = Mock() + stream = AnsiToWin32(mockStdout) + stream.wrapped = Mock() + stream.write_and_convert = Mock() + stream.strip = False + stream.convert = False + + stream.write('abc') + + self.assertFalse(stream.write_and_convert.called) + self.assertEqual(stream.wrapped.write.call_args, (('abc',), {})) + + def assert_autoresets(self, convert, autoreset=True): + stream = AnsiToWin32(Mock()) + stream.convert = convert + stream.reset_all = Mock() + stream.autoreset = autoreset + stream.winterm = Mock() + + stream.write('abc') + + self.assertEqual(stream.reset_all.called, autoreset) + + def testWriteAutoresets(self): + self.assert_autoresets(convert=True) + self.assert_autoresets(convert=False) + self.assert_autoresets(convert=True, autoreset=False) + self.assert_autoresets(convert=False, autoreset=False) + + def testWriteAndConvertWritesPlainText(self): + stream = AnsiToWin32(Mock()) + stream.write_and_convert( 'abc' ) + self.assertEqual( stream.wrapped.write.call_args, (('abc',), {}) ) + + def testWriteAndConvertStripsAllValidAnsi(self): + stream = AnsiToWin32(Mock()) + stream.call_win32 = Mock() + data = [ + 'abc\033[mdef', + 'abc\033[0mdef', + 'abc\033[2mdef', + 'abc\033[02mdef', + 'abc\033[002mdef', + 'abc\033[40mdef', + 'abc\033[040mdef', + 'abc\033[0;1mdef', + 'abc\033[40;50mdef', + 'abc\033[50;30;40mdef', + 'abc\033[Adef', + 'abc\033[0Gdef', + 'abc\033[1;20;128Hdef', + ] + for datum in data: + stream.wrapped.write.reset_mock() + stream.write_and_convert( datum ) + self.assertEqual( + [args[0] for args in stream.wrapped.write.call_args_list], + [ ('abc',), ('def',) ] + ) + + def testWriteAndConvertSkipsEmptySnippets(self): + stream = AnsiToWin32(Mock()) + stream.call_win32 = Mock() + stream.write_and_convert( '\033[40m\033[41m' ) + self.assertFalse( stream.wrapped.write.called ) + + def testWriteAndConvertCallsWin32WithParamsAndCommand(self): + stream = AnsiToWin32(Mock()) + stream.convert = True + stream.call_win32 = Mock() + stream.extract_params = Mock(return_value='params') + data = { + 'abc\033[adef': ('a', 'params'), + 'abc\033[;;bdef': ('b', 'params'), + 'abc\033[0cdef': ('c', 'params'), + 'abc\033[;;0;;Gdef': ('G', 'params'), + 'abc\033[1;20;128Hdef': ('H', 'params'), + } + for datum, expected in data.items(): + stream.call_win32.reset_mock() + stream.write_and_convert( datum ) + self.assertEqual( stream.call_win32.call_args[0], expected ) + + def test_reset_all_shouldnt_raise_on_closed_orig_stdout(self): + stream = StringIO() + converter = AnsiToWin32(stream) + stream.close() + + converter.reset_all() + + def test_wrap_shouldnt_raise_on_closed_orig_stdout(self): + stream = StringIO() + stream.close() + with \ + patch("colorama.ansitowin32.os.name", "nt"), \ + patch("colorama.ansitowin32.winapi_test", lambda: True): + converter = AnsiToWin32(stream) + self.assertTrue(converter.strip) + self.assertFalse(converter.convert) + + def test_wrap_shouldnt_raise_on_missing_closed_attr(self): + with \ + patch("colorama.ansitowin32.os.name", "nt"), \ + patch("colorama.ansitowin32.winapi_test", lambda: True): + converter = AnsiToWin32(object()) + self.assertTrue(converter.strip) + self.assertFalse(converter.convert) + + def testExtractParams(self): + stream = AnsiToWin32(Mock()) + data = { + '': (0,), + ';;': (0,), + '2': (2,), + ';;002;;': (2,), + '0;1': (0, 1), + ';;003;;456;;': (3, 456), + '11;22;33;44;55': (11, 22, 33, 44, 55), + } + for datum, expected in data.items(): + self.assertEqual(stream.extract_params('m', datum), expected) + + def testCallWin32UsesLookup(self): + listener = Mock() + stream = AnsiToWin32(listener) + stream.win32_calls = { + 1: (lambda *_, **__: listener(11),), + 2: (lambda *_, **__: listener(22),), + 3: (lambda *_, **__: listener(33),), + } + stream.call_win32('m', (3, 1, 99, 2)) + self.assertEqual( + [a[0][0] for a in listener.call_args_list], + [33, 11, 22] ) + + def test_osc_codes(self): + mockStdout = Mock() + stream = AnsiToWin32(mockStdout, convert=True) + with patch('colorama.ansitowin32.winterm') as winterm: + data = [ + '\033]0\x07', # missing arguments + '\033]0;foo\x08', # wrong OSC command + '\033]0;colorama_test_title\x07', # should work + '\033]1;colorama_test_title\x07', # wrong set command + '\033]2;colorama_test_title\x07', # should work + '\033]' + ';' * 64 + '\x08', # see issue #247 + ] + for code in data: + stream.write(code) + self.assertEqual(winterm.set_title.call_count, 2) + + def test_native_windows_ansi(self): + with ExitStack() as stack: + def p(a, b): + stack.enter_context(patch(a, b, create=True)) + # Pretend to be on Windows + p("colorama.ansitowin32.os.name", "nt") + p("colorama.ansitowin32.winapi_test", lambda: True) + p("colorama.win32.winapi_test", lambda: True) + p("colorama.winterm.win32.windll", "non-None") + p("colorama.winterm.get_osfhandle", lambda _: 1234) + + # Pretend that our mock stream has native ANSI support + p( + "colorama.winterm.win32.GetConsoleMode", + lambda _: ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ) + SetConsoleMode = Mock() + p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode) + + stdout = Mock() + stdout.closed = False + stdout.isatty.return_value = True + stdout.fileno.return_value = 1 + + # Our fake console says it has native vt support, so AnsiToWin32 should + # enable that support and do nothing else. + stream = AnsiToWin32(stdout) + SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self.assertFalse(stream.strip) + self.assertFalse(stream.convert) + self.assertFalse(stream.should_wrap()) + + # Now let's pretend we're on an old Windows console, that doesn't have + # native ANSI support. + p("colorama.winterm.win32.GetConsoleMode", lambda _: 0) + SetConsoleMode = Mock() + p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode) + + stream = AnsiToWin32(stdout) + SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING) + self.assertTrue(stream.strip) + self.assertTrue(stream.convert) + self.assertTrue(stream.should_wrap()) + + +if __name__ == '__main__': + main() diff --git a/.venv/Lib/site-packages/colorama/tests/initialise_test.py b/.venv/Lib/site-packages/colorama/tests/initialise_test.py new file mode 100644 index 0000000..89f9b07 --- /dev/null +++ b/.venv/Lib/site-packages/colorama/tests/initialise_test.py @@ -0,0 +1,189 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import sys +from unittest import TestCase, main, skipUnless + +try: + from unittest.mock import patch, Mock +except ImportError: + from mock import patch, Mock + +from ..ansitowin32 import StreamWrapper +from ..initialise import init, just_fix_windows_console, _wipe_internal_state_for_tests +from .utils import osname, replace_by + +orig_stdout = sys.stdout +orig_stderr = sys.stderr + + +class InitTest(TestCase): + + @skipUnless(sys.stdout.isatty(), "sys.stdout is not a tty") + def setUp(self): + # sanity check + self.assertNotWrapped() + + def tearDown(self): + _wipe_internal_state_for_tests() + sys.stdout = orig_stdout + sys.stderr = orig_stderr + + def assertWrapped(self): + self.assertIsNot(sys.stdout, orig_stdout, 'stdout should be wrapped') + self.assertIsNot(sys.stderr, orig_stderr, 'stderr should be wrapped') + self.assertTrue(isinstance(sys.stdout, StreamWrapper), + 'bad stdout wrapper') + self.assertTrue(isinstance(sys.stderr, StreamWrapper), + 'bad stderr wrapper') + + def assertNotWrapped(self): + self.assertIs(sys.stdout, orig_stdout, 'stdout should not be wrapped') + self.assertIs(sys.stderr, orig_stderr, 'stderr should not be wrapped') + + @patch('colorama.initialise.reset_all') + @patch('colorama.ansitowin32.winapi_test', lambda *_: True) + @patch('colorama.ansitowin32.enable_vt_processing', lambda *_: False) + def testInitWrapsOnWindows(self, _): + with osname("nt"): + init() + self.assertWrapped() + + @patch('colorama.initialise.reset_all') + @patch('colorama.ansitowin32.winapi_test', lambda *_: False) + def testInitDoesntWrapOnEmulatedWindows(self, _): + with osname("nt"): + init() + self.assertNotWrapped() + + def testInitDoesntWrapOnNonWindows(self): + with osname("posix"): + init() + self.assertNotWrapped() + + def testInitDoesntWrapIfNone(self): + with replace_by(None): + init() + # We can't use assertNotWrapped here because replace_by(None) + # changes stdout/stderr already. + self.assertIsNone(sys.stdout) + self.assertIsNone(sys.stderr) + + def testInitAutoresetOnWrapsOnAllPlatforms(self): + with osname("posix"): + init(autoreset=True) + self.assertWrapped() + + def testInitWrapOffDoesntWrapOnWindows(self): + with osname("nt"): + init(wrap=False) + self.assertNotWrapped() + + def testInitWrapOffIncompatibleWithAutoresetOn(self): + self.assertRaises(ValueError, lambda: init(autoreset=True, wrap=False)) + + @patch('colorama.win32.SetConsoleTextAttribute') + @patch('colorama.initialise.AnsiToWin32') + def testAutoResetPassedOn(self, mockATW32, _): + with osname("nt"): + init(autoreset=True) + self.assertEqual(len(mockATW32.call_args_list), 2) + self.assertEqual(mockATW32.call_args_list[1][1]['autoreset'], True) + self.assertEqual(mockATW32.call_args_list[0][1]['autoreset'], True) + + @patch('colorama.initialise.AnsiToWin32') + def testAutoResetChangeable(self, mockATW32): + with osname("nt"): + init() + + init(autoreset=True) + self.assertEqual(len(mockATW32.call_args_list), 4) + self.assertEqual(mockATW32.call_args_list[2][1]['autoreset'], True) + self.assertEqual(mockATW32.call_args_list[3][1]['autoreset'], True) + + init() + self.assertEqual(len(mockATW32.call_args_list), 6) + self.assertEqual( + mockATW32.call_args_list[4][1]['autoreset'], False) + self.assertEqual( + mockATW32.call_args_list[5][1]['autoreset'], False) + + + @patch('colorama.initialise.atexit.register') + def testAtexitRegisteredOnlyOnce(self, mockRegister): + init() + self.assertTrue(mockRegister.called) + mockRegister.reset_mock() + init() + self.assertFalse(mockRegister.called) + + +class JustFixWindowsConsoleTest(TestCase): + def _reset(self): + _wipe_internal_state_for_tests() + sys.stdout = orig_stdout + sys.stderr = orig_stderr + + def tearDown(self): + self._reset() + + @patch("colorama.ansitowin32.winapi_test", lambda: True) + def testJustFixWindowsConsole(self): + if sys.platform != "win32": + # just_fix_windows_console should be a no-op + just_fix_windows_console() + self.assertIs(sys.stdout, orig_stdout) + self.assertIs(sys.stderr, orig_stderr) + else: + def fake_std(): + # Emulate stdout=not a tty, stderr=tty + # to check that we handle both cases correctly + stdout = Mock() + stdout.closed = False + stdout.isatty.return_value = False + stdout.fileno.return_value = 1 + sys.stdout = stdout + + stderr = Mock() + stderr.closed = False + stderr.isatty.return_value = True + stderr.fileno.return_value = 2 + sys.stderr = stderr + + for native_ansi in [False, True]: + with patch( + 'colorama.ansitowin32.enable_vt_processing', + lambda *_: native_ansi + ): + self._reset() + fake_std() + + # Regular single-call test + prev_stdout = sys.stdout + prev_stderr = sys.stderr + just_fix_windows_console() + self.assertIs(sys.stdout, prev_stdout) + if native_ansi: + self.assertIs(sys.stderr, prev_stderr) + else: + self.assertIsNot(sys.stderr, prev_stderr) + + # second call without resetting is always a no-op + prev_stdout = sys.stdout + prev_stderr = sys.stderr + just_fix_windows_console() + self.assertIs(sys.stdout, prev_stdout) + self.assertIs(sys.stderr, prev_stderr) + + self._reset() + fake_std() + + # If init() runs first, just_fix_windows_console should be a no-op + init() + prev_stdout = sys.stdout + prev_stderr = sys.stderr + just_fix_windows_console() + self.assertIs(prev_stdout, sys.stdout) + self.assertIs(prev_stderr, sys.stderr) + + +if __name__ == '__main__': + main() diff --git a/.venv/Lib/site-packages/colorama/tests/isatty_test.py b/.venv/Lib/site-packages/colorama/tests/isatty_test.py new file mode 100644 index 0000000..0f84e4b --- /dev/null +++ b/.venv/Lib/site-packages/colorama/tests/isatty_test.py @@ -0,0 +1,57 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import sys +from unittest import TestCase, main + +from ..ansitowin32 import StreamWrapper, AnsiToWin32 +from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY + + +def is_a_tty(stream): + return StreamWrapper(stream, None).isatty() + +class IsattyTest(TestCase): + + def test_TTY(self): + tty = StreamTTY() + self.assertTrue(is_a_tty(tty)) + with pycharm(): + self.assertTrue(is_a_tty(tty)) + + def test_nonTTY(self): + non_tty = StreamNonTTY() + self.assertFalse(is_a_tty(non_tty)) + with pycharm(): + self.assertFalse(is_a_tty(non_tty)) + + def test_withPycharm(self): + with pycharm(): + self.assertTrue(is_a_tty(sys.stderr)) + self.assertTrue(is_a_tty(sys.stdout)) + + def test_withPycharmTTYOverride(self): + tty = StreamTTY() + with pycharm(), replace_by(tty): + self.assertTrue(is_a_tty(tty)) + + def test_withPycharmNonTTYOverride(self): + non_tty = StreamNonTTY() + with pycharm(), replace_by(non_tty): + self.assertFalse(is_a_tty(non_tty)) + + def test_withPycharmNoneOverride(self): + with pycharm(): + with replace_by(None), replace_original_by(None): + self.assertFalse(is_a_tty(None)) + self.assertFalse(is_a_tty(StreamNonTTY())) + self.assertTrue(is_a_tty(StreamTTY())) + + def test_withPycharmStreamWrapped(self): + with pycharm(): + self.assertTrue(AnsiToWin32(StreamTTY()).stream.isatty()) + self.assertFalse(AnsiToWin32(StreamNonTTY()).stream.isatty()) + self.assertTrue(AnsiToWin32(sys.stdout).stream.isatty()) + self.assertTrue(AnsiToWin32(sys.stderr).stream.isatty()) + + +if __name__ == '__main__': + main() diff --git a/.venv/Lib/site-packages/colorama/tests/utils.py b/.venv/Lib/site-packages/colorama/tests/utils.py new file mode 100644 index 0000000..472fafb --- /dev/null +++ b/.venv/Lib/site-packages/colorama/tests/utils.py @@ -0,0 +1,49 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from contextlib import contextmanager +from io import StringIO +import sys +import os + + +class StreamTTY(StringIO): + def isatty(self): + return True + +class StreamNonTTY(StringIO): + def isatty(self): + return False + +@contextmanager +def osname(name): + orig = os.name + os.name = name + yield + os.name = orig + +@contextmanager +def replace_by(stream): + orig_stdout = sys.stdout + orig_stderr = sys.stderr + sys.stdout = stream + sys.stderr = stream + yield + sys.stdout = orig_stdout + sys.stderr = orig_stderr + +@contextmanager +def replace_original_by(stream): + orig_stdout = sys.__stdout__ + orig_stderr = sys.__stderr__ + sys.__stdout__ = stream + sys.__stderr__ = stream + yield + sys.__stdout__ = orig_stdout + sys.__stderr__ = orig_stderr + +@contextmanager +def pycharm(): + os.environ["PYCHARM_HOSTED"] = "1" + non_tty = StreamNonTTY() + with replace_by(non_tty), replace_original_by(non_tty): + yield + del os.environ["PYCHARM_HOSTED"] diff --git a/.venv/Lib/site-packages/colorama/tests/winterm_test.py b/.venv/Lib/site-packages/colorama/tests/winterm_test.py new file mode 100644 index 0000000..d0955f9 --- /dev/null +++ b/.venv/Lib/site-packages/colorama/tests/winterm_test.py @@ -0,0 +1,131 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import sys +from unittest import TestCase, main, skipUnless + +try: + from unittest.mock import Mock, patch +except ImportError: + from mock import Mock, patch + +from ..winterm import WinColor, WinStyle, WinTerm + + +class WinTermTest(TestCase): + + @patch('colorama.winterm.win32') + def testInit(self, mockWin32): + mockAttr = Mock() + mockAttr.wAttributes = 7 + 6 * 16 + 8 + mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr + term = WinTerm() + self.assertEqual(term._fore, 7) + self.assertEqual(term._back, 6) + self.assertEqual(term._style, 8) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + def testGetAttrs(self): + term = WinTerm() + + term._fore = 0 + term._back = 0 + term._style = 0 + self.assertEqual(term.get_attrs(), 0) + + term._fore = WinColor.YELLOW + self.assertEqual(term.get_attrs(), WinColor.YELLOW) + + term._back = WinColor.MAGENTA + self.assertEqual( + term.get_attrs(), + WinColor.YELLOW + WinColor.MAGENTA * 16) + + term._style = WinStyle.BRIGHT + self.assertEqual( + term.get_attrs(), + WinColor.YELLOW + WinColor.MAGENTA * 16 + WinStyle.BRIGHT) + + @patch('colorama.winterm.win32') + def testResetAll(self, mockWin32): + mockAttr = Mock() + mockAttr.wAttributes = 1 + 2 * 16 + 8 + mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr + term = WinTerm() + + term.set_console = Mock() + term._fore = -1 + term._back = -1 + term._style = -1 + + term.reset_all() + + self.assertEqual(term._fore, 1) + self.assertEqual(term._back, 2) + self.assertEqual(term._style, 8) + self.assertEqual(term.set_console.called, True) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + def testFore(self): + term = WinTerm() + term.set_console = Mock() + term._fore = 0 + + term.fore(5) + + self.assertEqual(term._fore, 5) + self.assertEqual(term.set_console.called, True) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + def testBack(self): + term = WinTerm() + term.set_console = Mock() + term._back = 0 + + term.back(5) + + self.assertEqual(term._back, 5) + self.assertEqual(term.set_console.called, True) + + @skipUnless(sys.platform.startswith("win"), "requires Windows") + def testStyle(self): + term = WinTerm() + term.set_console = Mock() + term._style = 0 + + term.style(22) + + self.assertEqual(term._style, 22) + self.assertEqual(term.set_console.called, True) + + @patch('colorama.winterm.win32') + def testSetConsole(self, mockWin32): + mockAttr = Mock() + mockAttr.wAttributes = 0 + mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr + term = WinTerm() + term.windll = Mock() + + term.set_console() + + self.assertEqual( + mockWin32.SetConsoleTextAttribute.call_args, + ((mockWin32.STDOUT, term.get_attrs()), {}) + ) + + @patch('colorama.winterm.win32') + def testSetConsoleOnStderr(self, mockWin32): + mockAttr = Mock() + mockAttr.wAttributes = 0 + mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr + term = WinTerm() + term.windll = Mock() + + term.set_console(on_stderr=True) + + self.assertEqual( + mockWin32.SetConsoleTextAttribute.call_args, + ((mockWin32.STDERR, term.get_attrs()), {}) + ) + + +if __name__ == '__main__': + main() diff --git a/.venv/Lib/site-packages/colorama/win32.py b/.venv/Lib/site-packages/colorama/win32.py new file mode 100644 index 0000000..841b0e2 --- /dev/null +++ b/.venv/Lib/site-packages/colorama/win32.py @@ -0,0 +1,180 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. + +# from winbase.h +STDOUT = -11 +STDERR = -12 + +ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + +try: + import ctypes + from ctypes import LibraryLoader + windll = LibraryLoader(ctypes.WinDLL) + from ctypes import wintypes +except (AttributeError, ImportError): + windll = None + SetConsoleTextAttribute = lambda *_: None + winapi_test = lambda *_: None +else: + from ctypes import byref, Structure, c_char, POINTER + + COORD = wintypes._COORD + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + def __str__(self): + return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( + self.dwSize.Y, self.dwSize.X + , self.dwCursorPosition.Y, self.dwCursorPosition.X + , self.wAttributes + , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right + , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X + ) + + _GetStdHandle = windll.kernel32.GetStdHandle + _GetStdHandle.argtypes = [ + wintypes.DWORD, + ] + _GetStdHandle.restype = wintypes.HANDLE + + _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo + _GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + POINTER(CONSOLE_SCREEN_BUFFER_INFO), + ] + _GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute + _SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + ] + _SetConsoleTextAttribute.restype = wintypes.BOOL + + _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition + _SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + COORD, + ] + _SetConsoleCursorPosition.restype = wintypes.BOOL + + _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA + _FillConsoleOutputCharacterA.argtypes = [ + wintypes.HANDLE, + c_char, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputCharacterA.restype = wintypes.BOOL + + _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute + _FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputAttribute.restype = wintypes.BOOL + + _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW + _SetConsoleTitleW.argtypes = [ + wintypes.LPCWSTR + ] + _SetConsoleTitleW.restype = wintypes.BOOL + + _GetConsoleMode = windll.kernel32.GetConsoleMode + _GetConsoleMode.argtypes = [ + wintypes.HANDLE, + POINTER(wintypes.DWORD) + ] + _GetConsoleMode.restype = wintypes.BOOL + + _SetConsoleMode = windll.kernel32.SetConsoleMode + _SetConsoleMode.argtypes = [ + wintypes.HANDLE, + wintypes.DWORD + ] + _SetConsoleMode.restype = wintypes.BOOL + + def _winapi_test(handle): + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return bool(success) + + def winapi_test(): + return any(_winapi_test(h) for h in + (_GetStdHandle(STDOUT), _GetStdHandle(STDERR))) + + def GetConsoleScreenBufferInfo(stream_id=STDOUT): + handle = _GetStdHandle(stream_id) + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return csbi + + def SetConsoleTextAttribute(stream_id, attrs): + handle = _GetStdHandle(stream_id) + return _SetConsoleTextAttribute(handle, attrs) + + def SetConsoleCursorPosition(stream_id, position, adjust=True): + position = COORD(*position) + # If the position is out of range, do nothing. + if position.Y <= 0 or position.X <= 0: + return + # Adjust for Windows' SetConsoleCursorPosition: + # 1. being 0-based, while ANSI is 1-based. + # 2. expecting (x,y), while ANSI uses (y,x). + adjusted_position = COORD(position.Y - 1, position.X - 1) + if adjust: + # Adjust for viewport's scroll position + sr = GetConsoleScreenBufferInfo(STDOUT).srWindow + adjusted_position.Y += sr.Top + adjusted_position.X += sr.Left + # Resume normal processing + handle = _GetStdHandle(stream_id) + return _SetConsoleCursorPosition(handle, adjusted_position) + + def FillConsoleOutputCharacter(stream_id, char, length, start): + handle = _GetStdHandle(stream_id) + char = c_char(char.encode()) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + success = _FillConsoleOutputCharacterA( + handle, char, length, start, byref(num_written)) + return num_written.value + + def FillConsoleOutputAttribute(stream_id, attr, length, start): + ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' + handle = _GetStdHandle(stream_id) + attribute = wintypes.WORD(attr) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + return _FillConsoleOutputAttribute( + handle, attribute, length, start, byref(num_written)) + + def SetConsoleTitle(title): + return _SetConsoleTitleW(title) + + def GetConsoleMode(handle): + mode = wintypes.DWORD() + success = _GetConsoleMode(handle, byref(mode)) + if not success: + raise ctypes.WinError() + return mode.value + + def SetConsoleMode(handle, mode): + success = _SetConsoleMode(handle, mode) + if not success: + raise ctypes.WinError() diff --git a/.venv/Lib/site-packages/colorama/winterm.py b/.venv/Lib/site-packages/colorama/winterm.py new file mode 100644 index 0000000..aad867e --- /dev/null +++ b/.venv/Lib/site-packages/colorama/winterm.py @@ -0,0 +1,195 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +try: + from msvcrt import get_osfhandle +except ImportError: + def get_osfhandle(_): + raise OSError("This isn't windows!") + + +from . import win32 + +# from wincon.h +class WinColor(object): + BLACK = 0 + BLUE = 1 + GREEN = 2 + CYAN = 3 + RED = 4 + MAGENTA = 5 + YELLOW = 6 + GREY = 7 + +# from wincon.h +class WinStyle(object): + NORMAL = 0x00 # dim text, dim background + BRIGHT = 0x08 # bright text, dim background + BRIGHT_BACKGROUND = 0x80 # dim text, bright background + +class WinTerm(object): + + def __init__(self): + self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes + self.set_attrs(self._default) + self._default_fore = self._fore + self._default_back = self._back + self._default_style = self._style + # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style. + # So that LIGHT_EX colors and BRIGHT style do not clobber each other, + # we track them separately, since LIGHT_EX is overwritten by Fore/Back + # and BRIGHT is overwritten by Style codes. + self._light = 0 + + def get_attrs(self): + return self._fore + self._back * 16 + (self._style | self._light) + + def set_attrs(self, value): + self._fore = value & 7 + self._back = (value >> 4) & 7 + self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND) + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + self._light = 0 + + def fore(self, fore=None, light=False, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + # Emulate LIGHT_EX with BRIGHT Style + if light: + self._light |= WinStyle.BRIGHT + else: + self._light &= ~WinStyle.BRIGHT + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, light=False, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style + if light: + self._light |= WinStyle.BRIGHT_BACKGROUND + else: + self._light &= ~WinStyle.BRIGHT_BACKGROUND + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + # I'm not currently tracking the position, so there is no default. + # position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_adjust(self, x, y, on_stderr=False): + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y + y, position.X + x) + win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False) + + def erase_screen(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen, and move cursor to (1,1) + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y + # get number of character cells before current cursor position + cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = cells_in_screen - cells_before_cursor + elif mode == 1: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_before_cursor + elif mode == 2: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_in_screen + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + if mode == 2: + # put the cursor where needed + win32.SetConsoleCursorPosition(handle, (1, 1)) + + def erase_line(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the line. + # 1 should clear from the cursor to the beginning of the line. + # 2 should clear the entire line. + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X + elif mode == 1: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwCursorPosition.X + elif mode == 2: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwSize.X + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + + def set_title(self, title): + win32.SetConsoleTitle(title) + + +def enable_vt_processing(fd): + if win32.windll is None or not win32.winapi_test(): + return False + + try: + handle = get_osfhandle(fd) + mode = win32.GetConsoleMode(handle) + win32.SetConsoleMode( + handle, + mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ) + + mode = win32.GetConsoleMode(handle) + if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING: + return True + # Can get TypeError in testsuite where 'fd' is a Mock() + except (OSError, TypeError): + return False diff --git a/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/INSTALLER b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/METADATA b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/METADATA new file mode 100644 index 0000000..0d84502 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/METADATA @@ -0,0 +1,565 @@ +Metadata-Version: 2.1 +Name: fastapi +Version: 0.115.12 +Summary: FastAPI framework, high performance, easy to learn, fast to code, ready for production +Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python +Classifier: Topic :: Internet +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development +Classifier: Typing :: Typed +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Framework :: AsyncIO +Classifier: Framework :: FastAPI +Classifier: Framework :: Pydantic +Classifier: Framework :: Pydantic :: 1 +Classifier: Framework :: Pydantic :: 2 +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers +Classifier: Topic :: Internet :: WWW/HTTP +Project-URL: Homepage, https://github.com/fastapi/fastapi +Project-URL: Documentation, https://fastapi.tiangolo.com/ +Project-URL: Repository, https://github.com/fastapi/fastapi +Project-URL: Issues, https://github.com/fastapi/fastapi/issues +Project-URL: Changelog, https://fastapi.tiangolo.com/release-notes/ +Requires-Python: >=3.8 +Requires-Dist: starlette<0.47.0,>=0.40.0 +Requires-Dist: pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4 +Requires-Dist: typing-extensions>=4.8.0 +Provides-Extra: standard +Requires-Dist: fastapi-cli[standard]>=0.0.5; extra == "standard" +Requires-Dist: httpx>=0.23.0; extra == "standard" +Requires-Dist: jinja2>=3.1.5; extra == "standard" +Requires-Dist: python-multipart>=0.0.18; extra == "standard" +Requires-Dist: email-validator>=2.0.0; extra == "standard" +Requires-Dist: uvicorn[standard]>=0.12.0; extra == "standard" +Provides-Extra: all +Requires-Dist: fastapi-cli[standard]>=0.0.5; extra == "all" +Requires-Dist: httpx>=0.23.0; extra == "all" +Requires-Dist: jinja2>=3.1.5; extra == "all" +Requires-Dist: python-multipart>=0.0.18; extra == "all" +Requires-Dist: itsdangerous>=1.1.0; extra == "all" +Requires-Dist: pyyaml>=5.3.1; extra == "all" +Requires-Dist: ujson!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,>=4.0.1; extra == "all" +Requires-Dist: orjson>=3.2.1; extra == "all" +Requires-Dist: email-validator>=2.0.0; extra == "all" +Requires-Dist: uvicorn[standard]>=0.12.0; extra == "all" +Requires-Dist: pydantic-settings>=2.0.0; extra == "all" +Requires-Dist: pydantic-extra-types>=2.0.0; extra == "all" +Description-Content-Type: text/markdown + + +

+ FastAPI framework, high performance, easy to learn, fast to code, ready for production +

+

+ + Test + + + Coverage + + + Package version + + + Supported Python versions + +

+ +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/fastapi/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints. + +The key features are: + +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. + +* estimation based on tests on an internal development team, building production applications. + +## Sponsors + + + + + + + + + + + + + + + + + + + + + + + + +Other sponsors + +## Opinions + +"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" + +
Kabir Khan - Microsoft (ref)
+ +--- + +"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" + +
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
+ +--- + +"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" + +
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
+ +--- + +"_I’m over the moon excited about **FastAPI**. It’s so fun!_" + +
Brian Okken - Python Bytes podcast host (ref)
+ +--- + +"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" + +
Timothy Crosley - Hug creator (ref)
+ +--- + +"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" + +"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" + +
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
+ +--- + +"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._" + +
Deon Pillsbury - Cisco (ref)
+ +--- + +## **Typer**, the FastAPI of CLIs + + + +If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. + +**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 + +## Requirements + +FastAPI stands on the shoulders of giants: + +* Starlette for the web parts. +* Pydantic for the data parts. + +## Installation + +Create and activate a virtual environment and then install FastAPI: + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +**Note**: Make sure you put `"fastapi[standard]"` in quotes to ensure it works in all terminals. + +## Example + +### Create it + +* Create a file `main.py` with: + +```Python +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +
+Or use async def... + +If your code uses `async` / `await`, use `async def`: + +```Python hl_lines="9 14" +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +**Note**: + +If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. + +
+ +### Run it + +Run the server with: + +
+ +```console +$ fastapi dev main.py + + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + │ fastapi run │ + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +
+About the command fastapi dev main.py... + +The command `fastapi dev` reads your `main.py` file, detects the **FastAPI** app in it, and starts a server using Uvicorn. + +By default, `fastapi dev` will start with auto-reload enabled for local development. + +You can read more about it in the FastAPI CLI docs. + +
+ +### Check it + +Open your browser at http://127.0.0.1:8000/items/5?q=somequery. + +You will see the JSON response as: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +You already created an API that: + +* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. +* Both _paths_ take `GET` operations (also known as HTTP _methods_). +* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. +* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. + +### Interactive API docs + +Now go to http://127.0.0.1:8000/docs. + +You will see the automatic interactive API documentation (provided by Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +### Alternative API docs + +And now, go to http://127.0.0.1:8000/redoc. + +You will see the alternative automatic documentation (provided by ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## Example upgrade + +Now modify the file `main.py` to receive a body from a `PUT` request. + +Declare the body using standard Python types, thanks to Pydantic. + +```Python hl_lines="4 9-12 25-27" +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +The `fastapi dev` server should reload automatically. + +### Interactive API docs upgrade + +Now go to http://127.0.0.1:8000/docs. + +* The interactive API documentation will be automatically updated, including the new body: + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) + +* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: + +![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) + +### Alternative API docs upgrade + +And now, go to http://127.0.0.1:8000/redoc. + +* The alternative documentation will also reflect the new query parameter and body: + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### Recap + +In summary, you declare **once** the types of parameters, body, etc. as function parameters. + +You do that with standard modern Python types. + +You don't have to learn a new syntax, the methods or classes of a specific library, etc. + +Just standard **Python**. + +For example, for an `int`: + +```Python +item_id: int +``` + +or for a more complex `Item` model: + +```Python +item: Item +``` + +...and with that single declaration you get: + +* Editor support, including: + * Completion. + * Type checks. +* Validation of data: + * Automatic and clear errors when the data is invalid. + * Validation even for deeply nested JSON objects. +* Conversion of input data: coming from the network to Python data and types. Reading from: + * JSON. + * Path parameters. + * Query parameters. + * Cookies. + * Headers. + * Forms. + * Files. +* Conversion of output data: converting from Python data and types to network data (as JSON): + * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). + * `datetime` objects. + * `UUID` objects. + * Database models. + * ...and many more. +* Automatic interactive API documentation, including 2 alternative user interfaces: + * Swagger UI. + * ReDoc. + +--- + +Coming back to the previous code example, **FastAPI** will: + +* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. +* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. + * If it is not, the client will see a useful, clear error. +* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. + * As the `q` parameter is declared with `= None`, it is optional. + * Without the `None` it would be required (as is the body in the case with `PUT`). +* For `PUT` requests to `/items/{item_id}`, read the body as JSON: + * Check that it has a required attribute `name` that should be a `str`. + * Check that it has a required attribute `price` that has to be a `float`. + * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. + * All this would also work for deeply nested JSON objects. +* Convert from and to JSON automatically. +* Document everything with OpenAPI, that can be used by: + * Interactive documentation systems. + * Automatic client code generation systems, for many languages. +* Provide 2 interactive documentation web interfaces directly. + +--- + +We just scratched the surface, but you already get the idea of how it all works. + +Try changing the line with: + +```Python + return {"item_name": item.name, "item_id": item_id} +``` + +...from: + +```Python + ... "item_name": item.name ... +``` + +...to: + +```Python + ... "item_price": item.price ... +``` + +...and see how your editor will auto-complete the attributes and know their types: + +![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) + +For a more complete example including more features, see the Tutorial - User Guide. + +**Spoiler alert**: the tutorial - user guide includes: + +* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. +* How to set **validation constraints** as `maximum_length` or `regex`. +* A very powerful and easy to use **Dependency Injection** system. +* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. +* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). +* **GraphQL** integration with Strawberry and other libraries. +* Many extra features (thanks to Starlette) as: + * **WebSockets** + * extremely easy tests based on HTTPX and `pytest` + * **CORS** + * **Cookie Sessions** + * ...and more. + +## Performance + +Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) + +To understand more about it, see the section Benchmarks. + +## Dependencies + +FastAPI depends on Pydantic and Starlette. + +### `standard` Dependencies + +When you install FastAPI with `pip install "fastapi[standard]"` it comes with the `standard` group of optional dependencies: + +Used by Pydantic: + +* email-validator - for email validation. + +Used by Starlette: + +* httpx - Required if you want to use the `TestClient`. +* jinja2 - Required if you want to use the default template configuration. +* python-multipart - Required if you want to support form "parsing", with `request.form()`. + +Used by FastAPI / Starlette: + +* uvicorn - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving. +* `fastapi-cli` - to provide the `fastapi` command. + +### Without `standard` Dependencies + +If you don't want to include the `standard` optional dependencies, you can install with `pip install fastapi` instead of `pip install "fastapi[standard]"`. + +### Additional Optional Dependencies + +There are some additional dependencies you might want to install. + +Additional optional Pydantic dependencies: + +* pydantic-settings - for settings management. +* pydantic-extra-types - for extra types to be used with Pydantic. + +Additional optional FastAPI dependencies: + +* orjson - Required if you want to use `ORJSONResponse`. +* ujson - Required if you want to use `UJSONResponse`. + +## License + +This project is licensed under the terms of the MIT license. diff --git a/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/RECORD b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/RECORD new file mode 100644 index 0000000..5092f26 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/RECORD @@ -0,0 +1,97 @@ +../../Scripts/fastapi.exe,sha256=vdrulCxQyTRupvsOiuIk6ttXktp16stR_F7eIdT2Zf0,108436 +fastapi-0.115.12.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +fastapi-0.115.12.dist-info/METADATA,sha256=mV7D-DjELmGAedtcxayASLcleQv41h4mqHQhlqtLu1s,27671 +fastapi-0.115.12.dist-info/RECORD,, +fastapi-0.115.12.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +fastapi-0.115.12.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90 +fastapi-0.115.12.dist-info/entry_points.txt,sha256=GCf-WbIZxyGT4MUmrPGj1cOHYZoGsNPHAvNkT6hnGeA,61 +fastapi-0.115.12.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086 +fastapi/__init__.py,sha256=sgu-sth5uRYC--mOXSta-IPYjnDKUrpRSz6LuvKHf9o,1082 +fastapi/__main__.py,sha256=bKePXLdO4SsVSM6r9SVoLickJDcR2c0cTOxZRKq26YQ,37 +fastapi/__pycache__/__init__.cpython-312.pyc,, +fastapi/__pycache__/__main__.cpython-312.pyc,, +fastapi/__pycache__/_compat.cpython-312.pyc,, +fastapi/__pycache__/applications.cpython-312.pyc,, +fastapi/__pycache__/background.cpython-312.pyc,, +fastapi/__pycache__/cli.cpython-312.pyc,, +fastapi/__pycache__/concurrency.cpython-312.pyc,, +fastapi/__pycache__/datastructures.cpython-312.pyc,, +fastapi/__pycache__/encoders.cpython-312.pyc,, +fastapi/__pycache__/exception_handlers.cpython-312.pyc,, +fastapi/__pycache__/exceptions.cpython-312.pyc,, +fastapi/__pycache__/logger.cpython-312.pyc,, +fastapi/__pycache__/param_functions.cpython-312.pyc,, +fastapi/__pycache__/params.cpython-312.pyc,, +fastapi/__pycache__/requests.cpython-312.pyc,, +fastapi/__pycache__/responses.cpython-312.pyc,, +fastapi/__pycache__/routing.cpython-312.pyc,, +fastapi/__pycache__/staticfiles.cpython-312.pyc,, +fastapi/__pycache__/templating.cpython-312.pyc,, +fastapi/__pycache__/testclient.cpython-312.pyc,, +fastapi/__pycache__/types.cpython-312.pyc,, +fastapi/__pycache__/utils.cpython-312.pyc,, +fastapi/__pycache__/websockets.cpython-312.pyc,, +fastapi/_compat.py,sha256=Rg7kA7uue4Z6yr8T7hf8b7G6PeC_06mK004Nnykijfk,23953 +fastapi/applications.py,sha256=Ix-o9pQAWhEDf9J0Q1hZ0nBB1uP72c-Y3oiYzvrwqiM,176316 +fastapi/background.py,sha256=rouLirxUANrcYC824MSMypXL_Qb2HYg2YZqaiEqbEKI,1768 +fastapi/cli.py,sha256=OYhZb0NR_deuT5ofyPF2NoNBzZDNOP8Salef2nk-HqA,418 +fastapi/concurrency.py,sha256=MirfowoSpkMQZ8j_g0ZxaQKpV6eB3G-dB5TgcXCrgEA,1424 +fastapi/datastructures.py,sha256=b2PEz77XGq-u3Ur1Inwk0AGjOsQZO49yF9C7IPJ15cY,5766 +fastapi/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +fastapi/dependencies/__pycache__/__init__.cpython-312.pyc,, +fastapi/dependencies/__pycache__/models.cpython-312.pyc,, +fastapi/dependencies/__pycache__/utils.cpython-312.pyc,, +fastapi/dependencies/models.py,sha256=Pjl6vx-4nZ5Tta9kJa3-RfQKkXtCpS09-FhMgs9eWNs,1507 +fastapi/dependencies/utils.py,sha256=SwOOdQYOC0E4thJ-n1a0ohslWN9duyasNY_JBdWPTsY,35971 +fastapi/encoders.py,sha256=LvwYmFeOz4tVwvgBoC5rvZnbr7hZr73KGrU8O7zSptU,11068 +fastapi/exception_handlers.py,sha256=MBrIOA-ugjJDivIi4rSsUJBdTsjuzN76q4yh0q1COKw,1332 +fastapi/exceptions.py,sha256=taNixuFEXb67lI1bnX1ubq8y8TseJ4yoPlWjyP0fTzk,4969 +fastapi/logger.py,sha256=I9NNi3ov8AcqbsbC9wl1X-hdItKgYt2XTrx1f99Zpl4,54 +fastapi/middleware/__init__.py,sha256=oQDxiFVcc1fYJUOIFvphnK7pTT5kktmfL32QXpBFvvo,58 +fastapi/middleware/__pycache__/__init__.cpython-312.pyc,, +fastapi/middleware/__pycache__/cors.cpython-312.pyc,, +fastapi/middleware/__pycache__/gzip.cpython-312.pyc,, +fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc,, +fastapi/middleware/__pycache__/trustedhost.cpython-312.pyc,, +fastapi/middleware/__pycache__/wsgi.cpython-312.pyc,, +fastapi/middleware/cors.py,sha256=ynwjWQZoc_vbhzZ3_ZXceoaSrslHFHPdoM52rXr0WUU,79 +fastapi/middleware/gzip.py,sha256=xM5PcsH8QlAimZw4VDvcmTnqQamslThsfe3CVN2voa0,79 +fastapi/middleware/httpsredirect.py,sha256=rL8eXMnmLijwVkH7_400zHri1AekfeBd6D6qs8ix950,115 +fastapi/middleware/trustedhost.py,sha256=eE5XGRxGa7c5zPnMJDGp3BxaL25k5iVQlhnv-Pk0Pss,109 +fastapi/middleware/wsgi.py,sha256=Z3Ue-7wni4lUZMvH3G9ek__acgYdJstbnpZX_HQAboY,79 +fastapi/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +fastapi/openapi/__pycache__/__init__.cpython-312.pyc,, +fastapi/openapi/__pycache__/constants.cpython-312.pyc,, +fastapi/openapi/__pycache__/docs.cpython-312.pyc,, +fastapi/openapi/__pycache__/models.cpython-312.pyc,, +fastapi/openapi/__pycache__/utils.cpython-312.pyc,, +fastapi/openapi/constants.py,sha256=adGzmis1L1HJRTE3kJ5fmHS_Noq6tIY6pWv_SFzoFDU,153 +fastapi/openapi/docs.py,sha256=XcQq-ZbQdC5sI0gIGu5MoHK1q-OFaqws7-ORTo6sjY4,10348 +fastapi/openapi/models.py,sha256=PqkxQiqcEgjKuhfUIWPZPQcyTcubtUCB3vcObLsB7VE,15397 +fastapi/openapi/utils.py,sha256=e00G_p0IdpiffBUaq31BUyiloXbpld8RryKYnYKisdY,23964 +fastapi/param_functions.py,sha256=JHNPLIYvoAwdnZZavIVsxOat8x23fX_Kl33reh7HKl8,64019 +fastapi/params.py,sha256=g450axUBQgQJODdtM7WBxZbQj9Z64inFvadrgHikBbU,28237 +fastapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +fastapi/requests.py,sha256=zayepKFcienBllv3snmWI20Gk0oHNVLU4DDhqXBb4LU,142 +fastapi/responses.py,sha256=QNQQlwpKhQoIPZTTWkpc9d_QGeGZ_aVQPaDV3nQ8m7c,1761 +fastapi/routing.py,sha256=qwk_Vm1k4vDGdeEskNCVYTK01fDgYikqfOS_lf0DqAc,176216 +fastapi/security/__init__.py,sha256=bO8pNmxqVRXUjfl2mOKiVZLn0FpBQ61VUYVjmppnbJw,881 +fastapi/security/__pycache__/__init__.cpython-312.pyc,, +fastapi/security/__pycache__/api_key.cpython-312.pyc,, +fastapi/security/__pycache__/base.cpython-312.pyc,, +fastapi/security/__pycache__/http.cpython-312.pyc,, +fastapi/security/__pycache__/oauth2.cpython-312.pyc,, +fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc,, +fastapi/security/__pycache__/utils.cpython-312.pyc,, +fastapi/security/api_key.py,sha256=cBI5Z4zWVjL1uJrsjTeLy7MafHPAO2HQPzTrpyoIYWA,9094 +fastapi/security/base.py,sha256=dl4pvbC-RxjfbWgPtCWd8MVU-7CB2SZ22rJDXVCXO6c,141 +fastapi/security/http.py,sha256=rWR2x-5CUsjWmRucYthwRig6MG1o-boyrr4Xo-PuuxU,13606 +fastapi/security/oauth2.py,sha256=xCo5j1qpze6CvEuJHIneOI0v2fodGVMpHHVnHpiLfoM,21589 +fastapi/security/open_id_connect_url.py,sha256=8vizZ2tGqEp1ur8SwtVgyHJhGAJ5AqahgcvSpaIioDI,2722 +fastapi/security/utils.py,sha256=bd8T0YM7UQD5ATKucr1bNtAvz_Y3__dVNAv5UebiPvc,293 +fastapi/staticfiles.py,sha256=iirGIt3sdY2QZXd36ijs3Cj-T0FuGFda3cd90kM9Ikw,69 +fastapi/templating.py,sha256=4zsuTWgcjcEainMJFAlW6-gnslm6AgOS1SiiDWfmQxk,76 +fastapi/testclient.py,sha256=nBvaAmX66YldReJNZXPOk1sfuo2Q6hs8bOvIaCep6LQ,66 +fastapi/types.py,sha256=nFb36sK3DSoqoyo7Miwy3meKK5UdFBgkAgLSzQlUVyI,383 +fastapi/utils.py,sha256=y8Bj5ttMaI9tS4D60OUgXqKnktBr99NdYUnHHV9LgoY,7948 +fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222 diff --git a/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/REQUESTED b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/WHEEL b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/WHEEL new file mode 100644 index 0000000..64b991e --- /dev/null +++ b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: pdm-backend (2.4.3) +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/entry_points.txt b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/entry_points.txt new file mode 100644 index 0000000..b81849e --- /dev/null +++ b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +fastapi = fastapi.cli:main + +[gui_scripts] + diff --git a/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/licenses/LICENSE b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/licenses/LICENSE new file mode 100644 index 0000000..3e92463 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi-0.115.12.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Sebastián Ramírez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/.venv/Lib/site-packages/fastapi/__init__.py b/.venv/Lib/site-packages/fastapi/__init__.py new file mode 100644 index 0000000..80eb783 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/__init__.py @@ -0,0 +1,25 @@ +"""FastAPI framework, high performance, easy to learn, fast to code, ready for production""" + +__version__ = "0.115.12" + +from starlette import status as status + +from .applications import FastAPI as FastAPI +from .background import BackgroundTasks as BackgroundTasks +from .datastructures import UploadFile as UploadFile +from .exceptions import HTTPException as HTTPException +from .exceptions import WebSocketException as WebSocketException +from .param_functions import Body as Body +from .param_functions import Cookie as Cookie +from .param_functions import Depends as Depends +from .param_functions import File as File +from .param_functions import Form as Form +from .param_functions import Header as Header +from .param_functions import Path as Path +from .param_functions import Query as Query +from .param_functions import Security as Security +from .requests import Request as Request +from .responses import Response as Response +from .routing import APIRouter as APIRouter +from .websockets import WebSocket as WebSocket +from .websockets import WebSocketDisconnect as WebSocketDisconnect diff --git a/.venv/Lib/site-packages/fastapi/__main__.py b/.venv/Lib/site-packages/fastapi/__main__.py new file mode 100644 index 0000000..fc36465 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/__main__.py @@ -0,0 +1,3 @@ +from fastapi.cli import main + +main() diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7459403437e0aacabe3d46ed8df8e7e416e5a3db GIT binary patch literal 1120 zcmYk5TTj$L6vwCRrF&o4Bybxg#qdpL06p%n-406$A(xe&M0m@E0b!G}KPkswO zgNfh7Pv9msCWZ&zOn5UscxJf7KK%PT=ggexIdl50Uauf{zWPgtOL>HTY2*4CxPptn z0H0BSFbXVe1p`5j4MaJdvrv!^3M?NLaKUhq6(bwlhHX}g%D8N}#41r0R}GheYq(~( z!s<~2Hw;&So49GX20Vxd4cCE(@Q~pKa0|B#H-X!@ZFmrP7!MmB0v^F5hFid+c+_y4 zjYZ>l-0(1)h$iu*;SqK#n!;0tN7?OY8c!P@1D?S%7V3@9PMp45BT~KG+;H{hVUIR_9zak~67(Ec>?DE*sjosFpOzkW z78jp%79XAKX3tvvqEtks^4z*K?W=Pk(p)Bf_dxJ84t9v_N^n(n6UIrf7BUJ}eSK$V z^VLV6COS1ZTJP!JHut+!{qM~$^PmUO;tJ>8P;azXX+q;bXUt#a%CA9DdeJ(3H5EFt zNz@^{3vW{@^#0N|^-~cl&Gr`kkW#6%dyC41$5JOQL&vsws!W3N&DTJrePgVK(&uqZ zef4J6>D!(caNqO#RnI%7LP8;6l%XktQ3Z)|(3gIVBnb9Bb~FN_ zp#K6IoeC<2ZC26i_DE3zhbU`rGa+!R-hLXJOOZnV6QZi6rlqc>p{1#1P|J{(7D&G& zE^C*ft<5qVX6lz>Slc$-mF*oizqy;I2d6R5+{rh`XI*8Xvo_!#!7qiq0nxA_r8u zdJstxE_hxThsyIhNv~ge7QqxU`dr+Dv-P;i4UpeC%d);B_h)1LQ}qnpJwtODTFTJl j3_Z=ziwwmQTij`h#U+UanR>}NnMKS%$(0PBL27c_`}+z=nHS5SG2!zMRBr8Fniu80F@HzN=i Y^8$$v%#4hTcNt_JGO)C=6|n(D0DWUddH?_b literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/_compat.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/_compat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b37e74820a8931d0e4acde748ff3fbfb4cf6b26d GIT binary patch literal 27894 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/background.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/background.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58aa09c1b9fec54238888ac743f06638ce8d11e5 GIT binary patch literal 2392 zcmcIm&5smC6tAA1Z*~|~76rsm84{f)Fq3%LH4`zcAV@+stBW4SF}=0j)w8|Z-PKmr z><26fi6p{7#e=~kAs#g7U*cugllBFTn&@rFVkDk?)!p+UMB~NI?(3?4ANAg^>iufI zsZ`1cp0BLd@~?S>eo~XclS>D?Z5Z4}HnKGj`C41^GwqC~<{3}-v+Z0w*1f!6Xczoq zyXcqNB@JZ|E!o+7S`wvQ&Y<(i&Rs`#p62f9>DTOL!6^QwCPpUGXM@eCc!qc$S@Ni% zMb%e{wc6#Z7uc7GSQUmIm1cv0Ng}Bo>1UZ0mF5X2{z6EtNSn8ksEI75lie*Cd;kSd z)Yfd&&e&R8w=*50Xv3*;$%jc`A)E_OiimWoqn0I<)h4!71KA zj%F9HPqg>kKpR?nVgVT?uAn66q3AM(XBJz`^Qa|V7GUP!rD2s=sxq-$CK9i^o`;tx zwg3-mW8z54F~OXQkOhL`Wgu2^glgWlu8h60bR` z;bdGx(?}CIb_~C^LHy98NkWT@i{YkRW=Y--XFg6NW7iKEmt!f!8Jxgrl8HYFLwsuR zscw{$uQqk?I(H>C;dHo;YY_!*o%+P}T3ASa0{c`5(xs}N3JJfm{=rLFx zw`~yHj=kznf_x&WW(;7#e2T|2PBT@Nb1Hj0z*lN1Ftrx0&5doDDq?U%#~Pz{HLev7 zPyCLuPA{-7-~Ah?D*Pol|4J3N7W9K^p0ssTYYdym74bsp8C9Nx=Yj;MKsBD~wQkQN z94`X8i=(*ECSb8R1b(DyJdse8CA=%5{OWr0PU@etGGqLq*r5vlyT?I%s@+C1B~65G zX1CCXS%kKqFw%ZxZbP{6fsH>N*_^2((q$w{x?MaojMd= z|1W(Eb_8yQoalv=N5#Q0G;&c^P|x9e05LwHL`>Ih(% zlOd>pNJJ;)tugLvgqwU47QTFN#G^5Z4IAir(2AoV9$iBZkE4ADZm#cCkKV~{SEujD zZ>!T^tnE~%w~N!eV1kTFR5ZhRsIZ9NEuAc9y+D-w#WhsKq%-JQ1E>@Q{}sVNE) z6ndyv>)D&s|HO;nr7Y>mleb_zNKd{=SG@UPzIpF`A0O|{?w3-j02rU$=H5pF@Ld&u z!^o2H9ZXIj4<2|VfRMB`0-oXNZ;UqSYjt4yb>JrUw6?Yh$K?Zfp>M(gX_!BhMT4Y9 z==f2~%?u}(j;PLf1K|YZq^Uq$@yIKXikrksIK|0S5Rm3&2Jo*tATr|tPBWT79~xTg zaC;-BUBRf_V|2$6(n);k#$o70-c6bW%t6kNupb0;E)zV%GV&bmJ)dh93i-rO{wFCk z^we?#e@ktcnhEzKnHHaPBk70iA?Gp2^>ZN;!r5g)4N5O|9yINzf^lK5JE6VAL`TMn zz3zLAuJL%Exl-86IPLwlRoWADIdDYBUUA$G<8&$BkL~#$i+c8Bf7=$mWVaHel)Ig6 zuz>T-Cw;D-Z~_)4;u^whIG?Vb%^V)(hF0ll_qaSPU;VK7tz7?V)rUs$?V~Y@@a57e z&SaNIt%i|SGS@BTis#Z+eTZ~=HB4eICu@EJu6`w+SA=ypjJ?{MUzy1kMdwue*z)3vr2Waq7=0YQAF`zss!(Hv-*27tN#HsQLOy{ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65e39795c3cdcea51520f004ea9f9ddcaebde82b GIT binary patch literal 1693 zcmb_c-D?zA6u%!c`!V}vb~kEEtr1OX(l#O~2qCqsYyBWr0;Vl4TZYN(jZQKLLL$ZLkqstw~evXemwNt%}!GBwRf0vfA`#T z&$;)W-?=|05*mVb&+NTAt0MG74uTeLf~`3K^QeSM*hXbsz*u5plQJ#PvQkjWYC&!2 zs2wVVVNS8bIUIRr* zW0WOIbePD|uu_VBsVr?9qC&DtJL6B~s1y4c!>c-`={kZccSdRTx*&BlCmZOWSoEfEx>)J5U z!pEFfl>4P}%n_EY4_n+5y6KiHHWSPz`k+tDvLE71jB&?o9=353nE*-<+WG;s{RqQV zi=Fur4lZUAN0Fe76(DM8L<#V2AK2c21bq&+b9A15rK;$@GDqeiZ6Xxdjzj{q+mFO{ zaeyFBJixm>q!_*$zDJgTG_vhPB-;@&s-hF<>yz+@4wnFF#;>IKqEy4Kz+L6cv z9&04Hg&L`mk!+yf?O)k9+~k}h_k9AXC|Es7Yt#b|*YRaI>m>PtRGa-c9!C@OG8)JC z={WAB7oW;>0V%?ElQ=OhYllZc_)%SFKuWIbzG^sC%k`teZIa24NXAHp`Vlajumo5| zKb$aGMOd!mD?`9GUlG7jKcVY_8zvhv%u!wMRJhEGA8#yb#`Q5U%^me5Guoj2<>Y&d zAA5{@#Y;xHc$#^m!mSi9StZssz}*pM3a@y^@I-FleA_jz?Tl@Bqs4O&fbq7|?uc9L zzQ&wu#S7L@(Su5O7s_K43d<`F!y=<%9fNu^$2ez>S?#V={Wo5&zn2SyzX{t)!SDS9 z;y3hS3O&!F?BT`ZtC{wx3w4rEwa3}^#i85wclK(wXD!*Y8tDO(RCdO_>8>R^R+1g- z@#IYZ&Hjb#f><2a8tAQraJgM72#R>a8gC-ivw`;GfSkDBk)Eh1KliM9bs! zzWHOfj;*D;R?=PTd(!i7sF`<-x(b2Y-AA9A@4IhnXZeOeE`4Y*8ht zO_NOx?MbhvGt+0EQ$T;$6X5nAuLy^^9?$Q?Gy8L0=r+!ED0jku9vlbVavRPaRhAE_ z5_e!wFCWDccSZAAy4;hC=2P@xA3pVg_VE1#;76J?eUwsR`cakw?o9bw^IC0dh|3qi z4*?@A=L(mJ=CT60ENnm3;8*t!y?9zqNh6;`vvu`px!5F?*=-wfd)3vx!(!FWHW+7 ziRr-`pG}c^2$9x0*@w0D$lh7L5_!E&VeqS(nLV|j%${3O4?I&ru|>gdQ`+$(5anMs C#+fVt literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3a0485bea8e37fb25a9112d8cafc096e49bc839 GIT binary patch literal 8165 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/logger.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/logger.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08a68445a8e599ade1e8505f709841311f13c0dc GIT binary patch literal 306 zcmX@j%ge<81Z$G5GR%PVV-N=hn4yf%20+Gih7^Vr#vFzy1}277hAbwSA_Senw3-pZ zi(;x|)MS1M;`rTSPfIK=Ni4|JWWL3olb@cRnU{WxGd;D$2gpt>y2S<(OD$T-@EN51 zSEjR7OlWaxQE^OgVs4B}YH@Z+enCudW=g6;Kv8~HYH~?&j2qBE#{f@-vSJ0doW$bn z7>~r{?9?I!m;9{!7`?L8ys{Xd%%qs&%#u{y0-#i4dTMbD)D`*=>+}jLZ*kb<=BJeA vq}mmMf&k?7Vksc;ftit!@iqhB15T+9@#~yY7dfS7SYGB-ZDcFr07?S@)K^kb literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aaaa9523634254353cf3d41e54dca0d08d6082df GIT binary patch literal 35117 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 ODH8wPZ6Orr`u-nH#?CJQ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/params.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/params.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae27a60a7839db8821946efdf6f1f01ddeea20c0 GIT binary patch literal 25062 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 M=KOCs_@Q_F|7bU?6951J literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/requests.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/requests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f4d7b48909f2df6816991ad4babfc4779c52000 GIT binary patch literal 295 zcmXv{yGjF55WTY-h!0|6ZL#eZS+NbGNJ5ApvY6O}+gx`?z1hcI@7yfpCv5!&!9Vd2 z7PLuaC#17-7R|uSoHLJe-g~`0!SR+19-q0dTe1!BA8d{oyogk!kc$H504??ydvg!O z$`3nV?P&E7Ve63JbMesf4p*&Ln2$o@^q`uu1+TwcYd75T}A z19?kmNlsNEM@X47m7MAngV>q{3zHlxau@MsP*X6@6=iawk_>GyG7BT48p~Q<={rf< wVZY*)dO(u*{G=*7%Q6rT0&`e$p$ancqjL7n~})hMx%iYO{oC992_l+>}}^pM49wee1pP1n0- zW}Vh<4^`Czm5|z7d*HyeN)HGj!8KJAVaWFQ&0tgi5wN66 zK9&H>s_bJKa9oZ1I1V_WCVZR#oK%xOP6AG;DFLOp*OaBGN{G^GI)*MFL%EHNeMH_A zgY&2vUz-MPW{y!H#ojno39Lvb5%<>?L~6U9Z-%g5W}RTF{n!p*mE_P61{2K ztC6zO2yPchDIDx#0QV6=s$d{BW(cZi#E4{wL|%~$>9(ZCJ90kmWiAh%s{6Zwp>Y+a z;Z1Ox!EV@HBY4eRU7KnVy5i8LZZ`;SuQSKSmbpxI+QF`a8KI_bnO%Yn-PO5<`zk2D z+XbQg~+l#xIR?g5RBL(aWk~6 zM=Xh!KyOH!p@$mE%a!hA5a1>p8ZQ%w7F^A;h`|HfW&{rGM~5PPFOXD-*7>5{1ynDkY`>S0=n zQRG>n)#1Um4MMy7hb~eGw1@ez-Ue|KeV^Um%i>km$Sap1e` zsi)bqkF#gLx%wn~p_ja{3u{p2VZuvlnyoj9rg@5{H65dEah%q)kK4KxS`wND1*d70 zC)kshJ1$}L6~GS>eHA)-kc)IAxP`p|;x_svCd> zpt*bF&W-K#_-Ey(6DJ=}ocwC#>ATg(@Jv+q#Mr0;g`QIU`mWH|zzgLx9bn24KIGy! z{MaE7e=$Xp6;2fBI8UGpmhs8?tQU7XE%?v`JRFA?9kqz-5;%46MP-zyhVqNSriVOaCgyuka`(~Rhhq}|0I+XHb1oZV!Hhfwyc_w%*oTYEUJOO@;g6NBaAnc%n zKcO=_;Zxp0=XcOMJE+{3L?QP~8tp5hF!oH!?J0^dBix(YL%iQRlophG_1_S5&-+D` z9RC#wLT)=fdTW}y&Hd3&hsv?-Ozu{hE5}~2qp?;PP;y Lloz*72q6Ci10`@Y literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/routing.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/routing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc4c28ba404d0886d2c50f9bdc07e435ac590698 GIT binary patch literal 82038 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 aSHuCdoe_wOL5vT~jEs!;7@UjPfE)m3R!wmL literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/testclient.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/testclient.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45f10fd2d87f3797df6fdf795e9dfb790431e226 GIT binary patch literal 264 zcmX@j%ge<81Z$G5GMs?)V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zYX zA*sbB&N-Q>c_n_DOt(aeOA?E6QcFrw^-6#e$zX{hW}w=Y44*+-e&sq_#e^2878SDY)ck z<;UoirRJ5z_+%!<6la#C>J|W{64O(QW72?{5(_f*5%%g8RNmsS$<0qG%}KQ@;sDyt V2*kx8#s_9bM#g&#mPKqp4gkpcOP&A# literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/types.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/types.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a54d50600335035949edf4f9e13f51f2d72d7b1f GIT binary patch literal 775 zcmXw1J4_To7@oa-AA1~+vrvq=M4Br+8XpM|<9P=WBZTC{VzbS#`vVL6%FIIUDzU?k z&ZEN)0>#-;(%alc6LE|&##q>y&{;XNa8rE$>-+wHULOmEJmTxro!)pYA@oZGgQZ_P zmp$&hLmu*^0NKLk0x8VdIZ60(Acu;rgsQEEnyrPpt%rtfgr;qVc{`tRlwc^dY)e8p z1g58g>S>^?!}qYZFlf>ZDY4XT`=uEyJu$IMJ1_;h67b17Ab&M`K3H{rsPdh6V2z8n`bQ(an(l~LNz6a%H5^sW=QfD61 zw6?rZ-l66B0Mjk!8FsgTlpFD8>{NFk+Hqd^s}A*3xR>w}UI*%|aS=}ZsqB!|gkR`v z7<=0RJSG!-F#ltS?s51lOOo^rS>Mr(AE@>P)%vLR8{PR?n&{o0>bB11^X9qHf3)zq z^t^{gzgpw_bDfzZ^^;ZIH7|-Hn(VX>rjG7+mE-B&tq1+m!!x;8n&}s3cdZMfu;=dI peY??-JM{zOaPp8I)el?mtKG>iJ*#(HAF93KCw-&#MkV4y{0FaI*#rOp literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/utils.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e234f1194a7514c50c6d89426e4d9c9df27700c1 GIT binary patch literal 8393 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4823e73110d63f0d3f9634ebdd1d872303ada89d GIT binary patch literal 344 zcmYL_&q~8U5XN_t6spi(^Z{}-2jfvFA{0YW6e$D|!d|w?sA>Le*jXce5}!fnoA?HS z_TtH#(3>Y`i`Iet_V>*&v%|g*2K$U+nU0>;bbna$7tSwijtE{^#xh>AiYJ^~c8HzC z*|JOQCN5_SFX(;Rwt#ng_b`i9T7V&QurpDPsj6#8ZF;y%$3_~s4ZQ9UjnpL=1K|tI za616COEa}+rAP9{Hl*ykx)_N&1dSq=m52ZfqZ$$C8TeDJ=JW=Ni9|D=UimHhlTu str: + a = self.field_info.alias + return a if a is not None else self.name + + @property + def required(self) -> bool: + return self.field_info.is_required() + + @property + def default(self) -> Any: + return self.get_default() + + @property + def type_(self) -> Any: + return self.field_info.annotation + + def __post_init__(self) -> None: + self._type_adapter: TypeAdapter[Any] = TypeAdapter( + Annotated[self.field_info.annotation, self.field_info] + ) + + def get_default(self) -> Any: + if self.field_info.is_required(): + return Undefined + return self.field_info.get_default(call_default_factory=True) + + def validate( + self, + value: Any, + values: Dict[str, Any] = {}, # noqa: B006 + *, + loc: Tuple[Union[int, str], ...] = (), + ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: + try: + return ( + self._type_adapter.validate_python(value, from_attributes=True), + None, + ) + except ValidationError as exc: + return None, _regenerate_error_with_loc( + errors=exc.errors(include_url=False), loc_prefix=loc + ) + + def serialize( + self, + value: Any, + *, + mode: Literal["json", "python"] = "json", + include: Union[IncEx, None] = None, + exclude: Union[IncEx, None] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> Any: + # What calls this code passes a value that already called + # self._type_adapter.validate_python(value) + return self._type_adapter.dump_python( + value, + mode=mode, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + def __hash__(self) -> int: + # Each ModelField is unique for our purposes, to allow making a dict from + # ModelField to its JSON Schema. + return id(self) + + def get_annotation_from_field_info( + annotation: Any, field_info: FieldInfo, field_name: str + ) -> Any: + return annotation + + def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: + return errors # type: ignore[return-value] + + def _model_rebuild(model: Type[BaseModel]) -> None: + model.model_rebuild() + + def _model_dump( + model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any + ) -> Any: + return model.model_dump(mode=mode, **kwargs) + + def _get_model_config(model: BaseModel) -> Any: + return model.model_config + + def get_schema_from_model_field( + *, + field: ModelField, + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + separate_input_output_schemas: bool = True, + ) -> Dict[str, Any]: + override_mode: Union[Literal["validation"], None] = ( + None if separate_input_output_schemas else "validation" + ) + # This expects that GenerateJsonSchema was already used to generate the definitions + json_schema = field_mapping[(field, override_mode or field.mode)] + if "$ref" not in json_schema: + # TODO remove when deprecating Pydantic v1 + # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207 + json_schema["title"] = ( + field.field_info.title or field.alias.title().replace("_", " ") + ) + return json_schema + + def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: + return {} + + def get_definitions( + *, + fields: List[ModelField], + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, + ) -> Tuple[ + Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + Dict[str, Dict[str, Any]], + ]: + override_mode: Union[Literal["validation"], None] = ( + None if separate_input_output_schemas else "validation" + ) + inputs = [ + (field, override_mode or field.mode, field._type_adapter.core_schema) + for field in fields + ] + field_mapping, definitions = schema_generator.generate_definitions( + inputs=inputs + ) + return field_mapping, definitions # type: ignore[return-value] + + def is_scalar_field(field: ModelField) -> bool: + from fastapi import params + + return field_annotation_is_scalar( + field.field_info.annotation + ) and not isinstance(field.field_info, params.Body) + + def is_sequence_field(field: ModelField) -> bool: + return field_annotation_is_sequence(field.field_info.annotation) + + def is_scalar_sequence_field(field: ModelField) -> bool: + return field_annotation_is_scalar_sequence(field.field_info.annotation) + + def is_bytes_field(field: ModelField) -> bool: + return is_bytes_or_nonable_bytes_annotation(field.type_) + + def is_bytes_sequence_field(field: ModelField) -> bool: + return is_bytes_sequence_annotation(field.type_) + + def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: + cls = type(field_info) + merged_field_info = cls.from_annotation(annotation) + new_field_info = copy(field_info) + new_field_info.metadata = merged_field_info.metadata + new_field_info.annotation = merged_field_info.annotation + return new_field_info + + def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: + origin_type = ( + get_origin(field.field_info.annotation) or field.field_info.annotation + ) + assert issubclass(origin_type, sequence_types) # type: ignore[arg-type] + return sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return] + + def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: + error = ValidationError.from_exception_data( + "Field required", [{"type": "missing", "loc": loc, "input": {}}] + ).errors(include_url=False)[0] + error["input"] = None + return error # type: ignore[return-value] + + def create_body_model( + *, fields: Sequence[ModelField], model_name: str + ) -> Type[BaseModel]: + field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields} + BodyModel: Type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload] + return BodyModel + + def get_model_fields(model: Type[BaseModel]) -> List[ModelField]: + return [ + ModelField(field_info=field_info, name=name) + for name, field_info in model.model_fields.items() + ] + +else: + from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX + from pydantic import AnyUrl as Url # noqa: F401 + from pydantic import ( # type: ignore[assignment] + BaseConfig as BaseConfig, # noqa: F401 + ) + from pydantic import ValidationError as ValidationError # noqa: F401 + from pydantic.class_validators import ( # type: ignore[no-redef] + Validator as Validator, # noqa: F401 + ) + from pydantic.error_wrappers import ( # type: ignore[no-redef] + ErrorWrapper as ErrorWrapper, # noqa: F401 + ) + from pydantic.errors import MissingError + from pydantic.fields import ( # type: ignore[attr-defined] + SHAPE_FROZENSET, + SHAPE_LIST, + SHAPE_SEQUENCE, + SHAPE_SET, + SHAPE_SINGLETON, + SHAPE_TUPLE, + SHAPE_TUPLE_ELLIPSIS, + ) + from pydantic.fields import FieldInfo as FieldInfo + from pydantic.fields import ( # type: ignore[no-redef,attr-defined] + ModelField as ModelField, # noqa: F401 + ) + + # Keeping old "Required" functionality from Pydantic V1, without + # shadowing typing.Required. + RequiredParam: Any = Ellipsis # type: ignore[no-redef] + from pydantic.fields import ( # type: ignore[no-redef,attr-defined] + Undefined as Undefined, + ) + from pydantic.fields import ( # type: ignore[no-redef, attr-defined] + UndefinedType as UndefinedType, # noqa: F401 + ) + from pydantic.schema import ( + field_schema, + get_flat_models_from_fields, + get_model_name_map, + model_process_schema, + ) + from pydantic.schema import ( # type: ignore[no-redef] # noqa: F401 + get_annotation_from_field_info as get_annotation_from_field_info, + ) + from pydantic.typing import ( # type: ignore[no-redef] + evaluate_forwardref as evaluate_forwardref, # noqa: F401 + ) + from pydantic.utils import ( # type: ignore[no-redef] + lenient_issubclass as lenient_issubclass, # noqa: F401 + ) + + GetJsonSchemaHandler = Any # type: ignore[assignment,misc] + JsonSchemaValue = Dict[str, Any] # type: ignore[misc] + CoreSchema = Any # type: ignore[assignment,misc] + + sequence_shapes = { + SHAPE_LIST, + SHAPE_SET, + SHAPE_FROZENSET, + SHAPE_TUPLE, + SHAPE_SEQUENCE, + SHAPE_TUPLE_ELLIPSIS, + } + sequence_shape_to_type = { + SHAPE_LIST: list, + SHAPE_SET: set, + SHAPE_TUPLE: tuple, + SHAPE_SEQUENCE: list, + SHAPE_TUPLE_ELLIPSIS: list, + } + + @dataclass + class GenerateJsonSchema: # type: ignore[no-redef] + ref_template: str + + class PydanticSchemaGenerationError(Exception): # type: ignore[no-redef] + pass + + def with_info_plain_validator_function( # type: ignore[misc] + function: Callable[..., Any], + *, + ref: Union[str, None] = None, + metadata: Any = None, + serialization: Any = None, + ) -> Any: + return {} + + def get_model_definitions( + *, + flat_models: Set[Union[Type[BaseModel], Type[Enum]]], + model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], + ) -> Dict[str, Any]: + definitions: Dict[str, Dict[str, Any]] = {} + for model in flat_models: + m_schema, m_definitions, m_nested_models = model_process_schema( + model, model_name_map=model_name_map, ref_prefix=REF_PREFIX + ) + definitions.update(m_definitions) + model_name = model_name_map[model] + if "description" in m_schema: + m_schema["description"] = m_schema["description"].split("\f")[0] + definitions[model_name] = m_schema + return definitions + + def is_pv1_scalar_field(field: ModelField) -> bool: + from fastapi import params + + field_info = field.field_info + if not ( + field.shape == SHAPE_SINGLETON # type: ignore[attr-defined] + and not lenient_issubclass(field.type_, BaseModel) + and not lenient_issubclass(field.type_, dict) + and not field_annotation_is_sequence(field.type_) + and not is_dataclass(field.type_) + and not isinstance(field_info, params.Body) + ): + return False + if field.sub_fields: # type: ignore[attr-defined] + if not all( + is_pv1_scalar_field(f) + for f in field.sub_fields # type: ignore[attr-defined] + ): + return False + return True + + def is_pv1_scalar_sequence_field(field: ModelField) -> bool: + if (field.shape in sequence_shapes) and not lenient_issubclass( # type: ignore[attr-defined] + field.type_, BaseModel + ): + if field.sub_fields is not None: # type: ignore[attr-defined] + for sub_field in field.sub_fields: # type: ignore[attr-defined] + if not is_pv1_scalar_field(sub_field): + return False + return True + if _annotation_is_sequence(field.type_): + return True + return False + + def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: + use_errors: List[Any] = [] + for error in errors: + if isinstance(error, ErrorWrapper): + new_errors = ValidationError( # type: ignore[call-arg] + errors=[error], model=RequestErrorModel + ).errors() + use_errors.extend(new_errors) + elif isinstance(error, list): + use_errors.extend(_normalize_errors(error)) + else: + use_errors.append(error) + return use_errors + + def _model_rebuild(model: Type[BaseModel]) -> None: + model.update_forward_refs() + + def _model_dump( + model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any + ) -> Any: + return model.dict(**kwargs) + + def _get_model_config(model: BaseModel) -> Any: + return model.__config__ # type: ignore[attr-defined] + + def get_schema_from_model_field( + *, + field: ModelField, + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + separate_input_output_schemas: bool = True, + ) -> Dict[str, Any]: + # This expects that GenerateJsonSchema was already used to generate the definitions + return field_schema( # type: ignore[no-any-return] + field, model_name_map=model_name_map, ref_prefix=REF_PREFIX + )[0] + + def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: + models = get_flat_models_from_fields(fields, known_models=set()) + return get_model_name_map(models) # type: ignore[no-any-return] + + def get_definitions( + *, + fields: List[ModelField], + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + separate_input_output_schemas: bool = True, + ) -> Tuple[ + Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + Dict[str, Dict[str, Any]], + ]: + models = get_flat_models_from_fields(fields, known_models=set()) + return {}, get_model_definitions( + flat_models=models, model_name_map=model_name_map + ) + + def is_scalar_field(field: ModelField) -> bool: + return is_pv1_scalar_field(field) + + def is_sequence_field(field: ModelField) -> bool: + return field.shape in sequence_shapes or _annotation_is_sequence(field.type_) # type: ignore[attr-defined] + + def is_scalar_sequence_field(field: ModelField) -> bool: + return is_pv1_scalar_sequence_field(field) + + def is_bytes_field(field: ModelField) -> bool: + return lenient_issubclass(field.type_, bytes) + + def is_bytes_sequence_field(field: ModelField) -> bool: + return field.shape in sequence_shapes and lenient_issubclass(field.type_, bytes) # type: ignore[attr-defined] + + def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: + return copy(field_info) + + def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: + return sequence_shape_to_type[field.shape](value) # type: ignore[no-any-return,attr-defined] + + def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: + missing_field_error = ErrorWrapper(MissingError(), loc=loc) # type: ignore[call-arg] + new_error = ValidationError([missing_field_error], RequestErrorModel) + return new_error.errors()[0] # type: ignore[return-value] + + def create_body_model( + *, fields: Sequence[ModelField], model_name: str + ) -> Type[BaseModel]: + BodyModel = create_model(model_name) + for f in fields: + BodyModel.__fields__[f.name] = f # type: ignore[index] + return BodyModel + + def get_model_fields(model: Type[BaseModel]) -> List[ModelField]: + return list(model.__fields__.values()) # type: ignore[attr-defined] + + +def _regenerate_error_with_loc( + *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] +) -> List[Dict[str, Any]]: + updated_loc_errors: List[Any] = [ + {**err, "loc": loc_prefix + err.get("loc", ())} + for err in _normalize_errors(errors) + ] + + return updated_loc_errors + + +def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: + if lenient_issubclass(annotation, (str, bytes)): + return False + return lenient_issubclass(annotation, sequence_types) + + +def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + for arg in get_args(annotation): + if field_annotation_is_sequence(arg): + return True + return False + return _annotation_is_sequence(annotation) or _annotation_is_sequence( + get_origin(annotation) + ) + + +def value_is_sequence(value: Any) -> bool: + return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type] + + +def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: + return ( + lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile)) + or _annotation_is_sequence(annotation) + or is_dataclass(annotation) + ) + + +def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + return any(field_annotation_is_complex(arg) for arg in get_args(annotation)) + + return ( + _annotation_is_complex(annotation) + or _annotation_is_complex(origin) + or hasattr(origin, "__pydantic_core_schema__") + or hasattr(origin, "__get_pydantic_core_schema__") + ) + + +def field_annotation_is_scalar(annotation: Any) -> bool: + # handle Ellipsis here to make tuple[int, ...] work nicely + return annotation is Ellipsis or not field_annotation_is_complex(annotation) + + +def field_annotation_is_scalar_sequence(annotation: Union[Type[Any], None]) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + at_least_one_scalar_sequence = False + for arg in get_args(annotation): + if field_annotation_is_scalar_sequence(arg): + at_least_one_scalar_sequence = True + continue + elif not field_annotation_is_scalar(arg): + return False + return at_least_one_scalar_sequence + return field_annotation_is_sequence(annotation) and all( + field_annotation_is_scalar(sub_annotation) + for sub_annotation in get_args(annotation) + ) + + +def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool: + if lenient_issubclass(annotation, bytes): + return True + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + for arg in get_args(annotation): + if lenient_issubclass(arg, bytes): + return True + return False + + +def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool: + if lenient_issubclass(annotation, UploadFile): + return True + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + for arg in get_args(annotation): + if lenient_issubclass(arg, UploadFile): + return True + return False + + +def is_bytes_sequence_annotation(annotation: Any) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + at_least_one = False + for arg in get_args(annotation): + if is_bytes_sequence_annotation(arg): + at_least_one = True + continue + return at_least_one + return field_annotation_is_sequence(annotation) and all( + is_bytes_or_nonable_bytes_annotation(sub_annotation) + for sub_annotation in get_args(annotation) + ) + + +def is_uploadfile_sequence_annotation(annotation: Any) -> bool: + origin = get_origin(annotation) + if origin is Union or origin is UnionType: + at_least_one = False + for arg in get_args(annotation): + if is_uploadfile_sequence_annotation(arg): + at_least_one = True + continue + return at_least_one + return field_annotation_is_sequence(annotation) and all( + is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation) + for sub_annotation in get_args(annotation) + ) + + +@lru_cache +def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]: + return get_model_fields(model) diff --git a/.venv/Lib/site-packages/fastapi/applications.py b/.venv/Lib/site-packages/fastapi/applications.py new file mode 100644 index 0000000..6d427cd --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/applications.py @@ -0,0 +1,4585 @@ +from enum import Enum +from typing import ( + Any, + Awaitable, + Callable, + Coroutine, + Dict, + List, + Optional, + Sequence, + Type, + TypeVar, + Union, +) + +from fastapi import routing +from fastapi.datastructures import Default, DefaultPlaceholder +from fastapi.exception_handlers import ( + http_exception_handler, + request_validation_exception_handler, + websocket_request_validation_exception_handler, +) +from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError +from fastapi.logger import logger +from fastapi.openapi.docs import ( + get_redoc_html, + get_swagger_ui_html, + get_swagger_ui_oauth2_redirect_html, +) +from fastapi.openapi.utils import get_openapi +from fastapi.params import Depends +from fastapi.types import DecoratedCallable, IncEx +from fastapi.utils import generate_unique_id +from starlette.applications import Starlette +from starlette.datastructures import State +from starlette.exceptions import HTTPException +from starlette.middleware import Middleware +from starlette.middleware.base import BaseHTTPMiddleware +from starlette.requests import Request +from starlette.responses import HTMLResponse, JSONResponse, Response +from starlette.routing import BaseRoute +from starlette.types import ASGIApp, Lifespan, Receive, Scope, Send +from typing_extensions import Annotated, Doc, deprecated + +AppType = TypeVar("AppType", bound="FastAPI") + + +class FastAPI(Starlette): + """ + `FastAPI` app class, the main entrypoint to use FastAPI. + + Read more in the + [FastAPI docs for First Steps](https://fastapi.tiangolo.com/tutorial/first-steps/). + + ## Example + + ```python + from fastapi import FastAPI + + app = FastAPI() + ``` + """ + + def __init__( + self: AppType, + *, + debug: Annotated[ + bool, + Doc( + """ + Boolean indicating if debug tracebacks should be returned on server + errors. + + Read more in the + [Starlette docs for Applications](https://www.starlette.io/applications/#instantiating-the-application). + """ + ), + ] = False, + routes: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + **Note**: you probably shouldn't use this parameter, it is inherited + from Starlette and supported for compatibility. + + --- + + A list of routes to serve incoming HTTP and WebSocket requests. + """ + ), + deprecated( + """ + You normally wouldn't use this parameter with FastAPI, it is inherited + from Starlette and supported for compatibility. + + In FastAPI, you normally would use the *path operation methods*, + like `app.get()`, `app.post()`, etc. + """ + ), + ] = None, + title: Annotated[ + str, + Doc( + """ + The title of the API. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more in the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(title="ChimichangApp") + ``` + """ + ), + ] = "FastAPI", + summary: Annotated[ + Optional[str], + Doc( + """ + A short summary of the API. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more in the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(summary="Deadpond's favorite app. Nuff said.") + ``` + """ + ), + ] = None, + description: Annotated[ + str, + Doc( + ''' + A description of the API. Supports Markdown (using + [CommonMark syntax](https://commonmark.org/)). + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more in the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI( + description=""" + ChimichangApp API helps you do awesome stuff. 🚀 + + ## Items + + You can **read items**. + + ## Users + + You will be able to: + + * **Create users** (_not implemented_). + * **Read users** (_not implemented_). + + """ + ) + ``` + ''' + ), + ] = "", + version: Annotated[ + str, + Doc( + """ + The version of the API. + + **Note** This is the version of your application, not the version of + the OpenAPI specification nor the version of FastAPI being used. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more in the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(version="0.0.1") + ``` + """ + ), + ] = "0.1.0", + openapi_url: Annotated[ + Optional[str], + Doc( + """ + The URL where the OpenAPI schema will be served from. + + If you set it to `None`, no OpenAPI schema will be served publicly, and + the default automatic endpoints `/docs` and `/redoc` will also be + disabled. + + Read more in the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#openapi-url). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(openapi_url="/api/v1/openapi.json") + ``` + """ + ), + ] = "/openapi.json", + openapi_tags: Annotated[ + Optional[List[Dict[str, Any]]], + Doc( + """ + A list of tags used by OpenAPI, these are the same `tags` you can set + in the *path operations*, like: + + * `@app.get("/users/", tags=["users"])` + * `@app.get("/items/", tags=["items"])` + + The order of the tags can be used to specify the order shown in + tools like Swagger UI, used in the automatic path `/docs`. + + It's not required to specify all the tags used. + + The tags that are not declared MAY be organized randomly or based + on the tools' logic. Each tag name in the list MUST be unique. + + The value of each item is a `dict` containing: + + * `name`: The name of the tag. + * `description`: A short description of the tag. + [CommonMark syntax](https://commonmark.org/) MAY be used for rich + text representation. + * `externalDocs`: Additional external documentation for this tag. If + provided, it would contain a `dict` with: + * `description`: A short description of the target documentation. + [CommonMark syntax](https://commonmark.org/) MAY be used for + rich text representation. + * `url`: The URL for the target documentation. Value MUST be in + the form of a URL. + + Read more in the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-tags). + + **Example** + + ```python + from fastapi import FastAPI + + tags_metadata = [ + { + "name": "users", + "description": "Operations with users. The **login** logic is also here.", + }, + { + "name": "items", + "description": "Manage items. So _fancy_ they have their own docs.", + "externalDocs": { + "description": "Items external docs", + "url": "https://fastapi.tiangolo.com/", + }, + }, + ] + + app = FastAPI(openapi_tags=tags_metadata) + ``` + """ + ), + ] = None, + servers: Annotated[ + Optional[List[Dict[str, Union[str, Any]]]], + Doc( + """ + A `list` of `dict`s with connectivity information to a target server. + + You would use it, for example, if your application is served from + different domains and you want to use the same Swagger UI in the + browser to interact with each of them (instead of having multiple + browser tabs open). Or if you want to leave fixed the possible URLs. + + If the servers `list` is not provided, or is an empty `list`, the + default value would be a `dict` with a `url` value of `/`. + + Each item in the `list` is a `dict` containing: + + * `url`: A URL to the target host. This URL supports Server Variables + and MAY be relative, to indicate that the host location is relative + to the location where the OpenAPI document is being served. Variable + substitutions will be made when a variable is named in `{`brackets`}`. + * `description`: An optional string describing the host designated by + the URL. [CommonMark syntax](https://commonmark.org/) MAY be used for + rich text representation. + * `variables`: A `dict` between a variable name and its value. The value + is used for substitution in the server's URL template. + + Read more in the + [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/#additional-servers). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI( + servers=[ + {"url": "https://stag.example.com", "description": "Staging environment"}, + {"url": "https://prod.example.com", "description": "Production environment"}, + ] + ) + ``` + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of global dependencies, they will be applied to each + *path operation*, including in sub-routers. + + Read more about it in the + [FastAPI docs for Global Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/global-dependencies/). + + **Example** + + ```python + from fastapi import Depends, FastAPI + + from .dependencies import func_dep_1, func_dep_2 + + app = FastAPI(dependencies=[Depends(func_dep_1), Depends(func_dep_2)]) + ``` + """ + ), + ] = None, + default_response_class: Annotated[ + Type[Response], + Doc( + """ + The default response class to be used. + + Read more in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). + + **Example** + + ```python + from fastapi import FastAPI + from fastapi.responses import ORJSONResponse + + app = FastAPI(default_response_class=ORJSONResponse) + ``` + """ + ), + ] = Default(JSONResponse), + redirect_slashes: Annotated[ + bool, + Doc( + """ + Whether to detect and redirect slashes in URLs when the client doesn't + use the same format. + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(redirect_slashes=True) # the default + + @app.get("/items/") + async def read_items(): + return [{"item_id": "Foo"}] + ``` + + With this app, if a client goes to `/items` (without a trailing slash), + they will be automatically redirected with an HTTP status code of 307 + to `/items/`. + """ + ), + ] = True, + docs_url: Annotated[ + Optional[str], + Doc( + """ + The path to the automatic interactive API documentation. + It is handled in the browser by Swagger UI. + + The default URL is `/docs`. You can disable it by setting it to `None`. + + If `openapi_url` is set to `None`, this will be automatically disabled. + + Read more in the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#docs-urls). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(docs_url="/documentation", redoc_url=None) + ``` + """ + ), + ] = "/docs", + redoc_url: Annotated[ + Optional[str], + Doc( + """ + The path to the alternative automatic interactive API documentation + provided by ReDoc. + + The default URL is `/redoc`. You can disable it by setting it to `None`. + + If `openapi_url` is set to `None`, this will be automatically disabled. + + Read more in the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#docs-urls). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(docs_url="/documentation", redoc_url="redocumentation") + ``` + """ + ), + ] = "/redoc", + swagger_ui_oauth2_redirect_url: Annotated[ + Optional[str], + Doc( + """ + The OAuth2 redirect endpoint for the Swagger UI. + + By default it is `/docs/oauth2-redirect`. + + This is only used if you use OAuth2 (with the "Authorize" button) + with Swagger UI. + """ + ), + ] = "/docs/oauth2-redirect", + swagger_ui_init_oauth: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + OAuth2 configuration for the Swagger UI, by default shown at `/docs`. + + Read more about the available configuration options in the + [Swagger UI docs](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/). + """ + ), + ] = None, + middleware: Annotated[ + Optional[Sequence[Middleware]], + Doc( + """ + List of middleware to be added when creating the application. + + In FastAPI you would normally do this with `app.add_middleware()` + instead. + + Read more in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/tutorial/middleware/). + """ + ), + ] = None, + exception_handlers: Annotated[ + Optional[ + Dict[ + Union[int, Type[Exception]], + Callable[[Request, Any], Coroutine[Any, Any, Response]], + ] + ], + Doc( + """ + A dictionary with handlers for exceptions. + + In FastAPI, you would normally use the decorator + `@app.exception_handler()`. + + Read more in the + [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/). + """ + ), + ] = None, + on_startup: Annotated[ + Optional[Sequence[Callable[[], Any]]], + Doc( + """ + A list of startup event handler functions. + + You should instead use the `lifespan` handlers. + + Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). + """ + ), + ] = None, + on_shutdown: Annotated[ + Optional[Sequence[Callable[[], Any]]], + Doc( + """ + A list of shutdown event handler functions. + + You should instead use the `lifespan` handlers. + + Read more in the + [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). + """ + ), + ] = None, + lifespan: Annotated[ + Optional[Lifespan[AppType]], + Doc( + """ + A `Lifespan` context manager handler. This replaces `startup` and + `shutdown` functions with a single context manager. + + Read more in the + [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). + """ + ), + ] = None, + terms_of_service: Annotated[ + Optional[str], + Doc( + """ + A URL to the Terms of Service for your API. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more at the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). + + **Example** + + ```python + app = FastAPI(terms_of_service="http://example.com/terms/") + ``` + """ + ), + ] = None, + contact: Annotated[ + Optional[Dict[str, Union[str, Any]]], + Doc( + """ + A dictionary with the contact information for the exposed API. + + It can contain several fields. + + * `name`: (`str`) The name of the contact person/organization. + * `url`: (`str`) A URL pointing to the contact information. MUST be in + the format of a URL. + * `email`: (`str`) The email address of the contact person/organization. + MUST be in the format of an email address. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more at the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). + + **Example** + + ```python + app = FastAPI( + contact={ + "name": "Deadpoolio the Amazing", + "url": "http://x-force.example.com/contact/", + "email": "dp@x-force.example.com", + } + ) + ``` + """ + ), + ] = None, + license_info: Annotated[ + Optional[Dict[str, Union[str, Any]]], + Doc( + """ + A dictionary with the license information for the exposed API. + + It can contain several fields. + + * `name`: (`str`) **REQUIRED** (if a `license_info` is set). The + license name used for the API. + * `identifier`: (`str`) An [SPDX](https://spdx.dev/) license expression + for the API. The `identifier` field is mutually exclusive of the `url` + field. Available since OpenAPI 3.1.0, FastAPI 0.99.0. + * `url`: (`str`) A URL to the license used for the API. This MUST be + the format of a URL. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more at the + [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). + + **Example** + + ```python + app = FastAPI( + license_info={ + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html", + } + ) + ``` + """ + ), + ] = None, + openapi_prefix: Annotated[ + str, + Doc( + """ + A URL prefix for the OpenAPI URL. + """ + ), + deprecated( + """ + "openapi_prefix" has been deprecated in favor of "root_path", which + follows more closely the ASGI standard, is simpler, and more + automatic. + """ + ), + ] = "", + root_path: Annotated[ + str, + Doc( + """ + A path prefix handled by a proxy that is not seen by the application + but is seen by external clients, which affects things like Swagger UI. + + Read more about it at the + [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(root_path="/api/v1") + ``` + """ + ), + ] = "", + root_path_in_servers: Annotated[ + bool, + Doc( + """ + To disable automatically generating the URLs in the `servers` field + in the autogenerated OpenAPI using the `root_path`. + + Read more about it in the + [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/#disable-automatic-server-from-root_path). + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI(root_path_in_servers=False) + ``` + """ + ), + ] = True, + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses to be shown in OpenAPI. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/). + + And in the + [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + OpenAPI callbacks that should apply to all *path operations*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + webhooks: Annotated[ + Optional[routing.APIRouter], + Doc( + """ + Add OpenAPI webhooks. This is similar to `callbacks` but it doesn't + depend on specific *path operations*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + **Note**: This is available since OpenAPI 3.1.0, FastAPI 0.99.0. + + Read more about it in the + [FastAPI docs for OpenAPI Webhooks](https://fastapi.tiangolo.com/advanced/openapi-webhooks/). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark all *path operations* as deprecated. You probably don't need it, + but it's available. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) all the *path operations* in the generated OpenAPI. + You probably don't need it, but it's available. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + swagger_ui_parameters: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Parameters to configure Swagger UI, the autogenerated interactive API + documentation (by default at `/docs`). + + Read more about it in the + [FastAPI docs about how to Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + separate_input_output_schemas: Annotated[ + bool, + Doc( + """ + Whether to generate separate OpenAPI schemas for request body and + response body when the results would be more precise. + + This is particularly useful when automatically generating clients. + + For example, if you have a model like: + + ```python + from pydantic import BaseModel + + class Item(BaseModel): + name: str + tags: list[str] = [] + ``` + + When `Item` is used for input, a request body, `tags` is not required, + the client doesn't have to provide it. + + But when using `Item` for output, for a response body, `tags` is always + available because it has a default value, even if it's just an empty + list. So, the client should be able to always expect it. + + In this case, there would be two different schemas, one for input and + another one for output. + """ + ), + ] = True, + **extra: Annotated[ + Any, + Doc( + """ + Extra keyword arguments to be stored in the app, not used by FastAPI + anywhere. + """ + ), + ], + ) -> None: + self.debug = debug + self.title = title + self.summary = summary + self.description = description + self.version = version + self.terms_of_service = terms_of_service + self.contact = contact + self.license_info = license_info + self.openapi_url = openapi_url + self.openapi_tags = openapi_tags + self.root_path_in_servers = root_path_in_servers + self.docs_url = docs_url + self.redoc_url = redoc_url + self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url + self.swagger_ui_init_oauth = swagger_ui_init_oauth + self.swagger_ui_parameters = swagger_ui_parameters + self.servers = servers or [] + self.separate_input_output_schemas = separate_input_output_schemas + self.extra = extra + self.openapi_version: Annotated[ + str, + Doc( + """ + The version string of OpenAPI. + + FastAPI will generate OpenAPI version 3.1.0, and will output that as + the OpenAPI version. But some tools, even though they might be + compatible with OpenAPI 3.1.0, might not recognize it as a valid. + + So you could override this value to trick those tools into using + the generated OpenAPI. Have in mind that this is a hack. But if you + avoid using features added in OpenAPI 3.1.0, it might work for your + use case. + + This is not passed as a parameter to the `FastAPI` class to avoid + giving the false idea that FastAPI would generate a different OpenAPI + schema. It is only available as an attribute. + + **Example** + + ```python + from fastapi import FastAPI + + app = FastAPI() + + app.openapi_version = "3.0.2" + ``` + """ + ), + ] = "3.1.0" + self.openapi_schema: Optional[Dict[str, Any]] = None + if self.openapi_url: + assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'" + assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'" + # TODO: remove when discarding the openapi_prefix parameter + if openapi_prefix: + logger.warning( + '"openapi_prefix" has been deprecated in favor of "root_path", which ' + "follows more closely the ASGI standard, is simpler, and more " + "automatic. Check the docs at " + "https://fastapi.tiangolo.com/advanced/sub-applications/" + ) + self.webhooks: Annotated[ + routing.APIRouter, + Doc( + """ + The `app.webhooks` attribute is an `APIRouter` with the *path + operations* that will be used just for documentation of webhooks. + + Read more about it in the + [FastAPI docs for OpenAPI Webhooks](https://fastapi.tiangolo.com/advanced/openapi-webhooks/). + """ + ), + ] = webhooks or routing.APIRouter() + self.root_path = root_path or openapi_prefix + self.state: Annotated[ + State, + Doc( + """ + A state object for the application. This is the same object for the + entire application, it doesn't change from request to request. + + You normally wouldn't use this in FastAPI, for most of the cases you + would instead use FastAPI dependencies. + + This is simply inherited from Starlette. + + Read more about it in the + [Starlette docs for Applications](https://www.starlette.io/applications/#storing-state-on-the-app-instance). + """ + ), + ] = State() + self.dependency_overrides: Annotated[ + Dict[Callable[..., Any], Callable[..., Any]], + Doc( + """ + A dictionary with overrides for the dependencies. + + Each key is the original dependency callable, and the value is the + actual dependency that should be called. + + This is for testing, to replace expensive dependencies with testing + versions. + + Read more about it in the + [FastAPI docs for Testing Dependencies with Overrides](https://fastapi.tiangolo.com/advanced/testing-dependencies/). + """ + ), + ] = {} + self.router: routing.APIRouter = routing.APIRouter( + routes=routes, + redirect_slashes=redirect_slashes, + dependency_overrides_provider=self, + on_startup=on_startup, + on_shutdown=on_shutdown, + lifespan=lifespan, + default_response_class=default_response_class, + dependencies=dependencies, + callbacks=callbacks, + deprecated=deprecated, + include_in_schema=include_in_schema, + responses=responses, + generate_unique_id_function=generate_unique_id_function, + ) + self.exception_handlers: Dict[ + Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]] + ] = {} if exception_handlers is None else dict(exception_handlers) + self.exception_handlers.setdefault(HTTPException, http_exception_handler) + self.exception_handlers.setdefault( + RequestValidationError, request_validation_exception_handler + ) + self.exception_handlers.setdefault( + WebSocketRequestValidationError, + # Starlette still has incorrect type specification for the handlers + websocket_request_validation_exception_handler, # type: ignore + ) + + self.user_middleware: List[Middleware] = ( + [] if middleware is None else list(middleware) + ) + self.middleware_stack: Union[ASGIApp, None] = None + self.setup() + + def openapi(self) -> Dict[str, Any]: + """ + Generate the OpenAPI schema of the application. This is called by FastAPI + internally. + + The first time it is called it stores the result in the attribute + `app.openapi_schema`, and next times it is called, it just returns that same + result. To avoid the cost of generating the schema every time. + + If you need to modify the generated OpenAPI schema, you could modify it. + + Read more in the + [FastAPI docs for OpenAPI](https://fastapi.tiangolo.com/how-to/extending-openapi/). + """ + if not self.openapi_schema: + self.openapi_schema = get_openapi( + title=self.title, + version=self.version, + openapi_version=self.openapi_version, + summary=self.summary, + description=self.description, + terms_of_service=self.terms_of_service, + contact=self.contact, + license_info=self.license_info, + routes=self.routes, + webhooks=self.webhooks.routes, + tags=self.openapi_tags, + servers=self.servers, + separate_input_output_schemas=self.separate_input_output_schemas, + ) + return self.openapi_schema + + def setup(self) -> None: + if self.openapi_url: + urls = (server_data.get("url") for server_data in self.servers) + server_urls = {url for url in urls if url} + + async def openapi(req: Request) -> JSONResponse: + root_path = req.scope.get("root_path", "").rstrip("/") + if root_path not in server_urls: + if root_path and self.root_path_in_servers: + self.servers.insert(0, {"url": root_path}) + server_urls.add(root_path) + return JSONResponse(self.openapi()) + + self.add_route(self.openapi_url, openapi, include_in_schema=False) + if self.openapi_url and self.docs_url: + + async def swagger_ui_html(req: Request) -> HTMLResponse: + root_path = req.scope.get("root_path", "").rstrip("/") + openapi_url = root_path + self.openapi_url + oauth2_redirect_url = self.swagger_ui_oauth2_redirect_url + if oauth2_redirect_url: + oauth2_redirect_url = root_path + oauth2_redirect_url + return get_swagger_ui_html( + openapi_url=openapi_url, + title=f"{self.title} - Swagger UI", + oauth2_redirect_url=oauth2_redirect_url, + init_oauth=self.swagger_ui_init_oauth, + swagger_ui_parameters=self.swagger_ui_parameters, + ) + + self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False) + + if self.swagger_ui_oauth2_redirect_url: + + async def swagger_ui_redirect(req: Request) -> HTMLResponse: + return get_swagger_ui_oauth2_redirect_html() + + self.add_route( + self.swagger_ui_oauth2_redirect_url, + swagger_ui_redirect, + include_in_schema=False, + ) + if self.openapi_url and self.redoc_url: + + async def redoc_html(req: Request) -> HTMLResponse: + root_path = req.scope.get("root_path", "").rstrip("/") + openapi_url = root_path + self.openapi_url + return get_redoc_html( + openapi_url=openapi_url, title=f"{self.title} - ReDoc" + ) + + self.add_route(self.redoc_url, redoc_html, include_in_schema=False) + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if self.root_path: + scope["root_path"] = self.root_path + await super().__call__(scope, receive, send) + + def add_api_route( + self, + path: str, + endpoint: Callable[..., Any], + *, + response_model: Any = Default(None), + status_code: Optional[int] = None, + tags: Optional[List[Union[str, Enum]]] = None, + dependencies: Optional[Sequence[Depends]] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + response_description: str = "Successful Response", + responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + deprecated: Optional[bool] = None, + methods: Optional[List[str]] = None, + operation_id: Optional[str] = None, + response_model_include: Optional[IncEx] = None, + response_model_exclude: Optional[IncEx] = None, + response_model_by_alias: bool = True, + response_model_exclude_unset: bool = False, + response_model_exclude_defaults: bool = False, + response_model_exclude_none: bool = False, + include_in_schema: bool = True, + response_class: Union[Type[Response], DefaultPlaceholder] = Default( + JSONResponse + ), + name: Optional[str] = None, + openapi_extra: Optional[Dict[str, Any]] = None, + generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( + generate_unique_id + ), + ) -> None: + self.router.add_api_route( + path, + endpoint=endpoint, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=methods, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def api_route( + self, + path: str, + *, + response_model: Any = Default(None), + status_code: Optional[int] = None, + tags: Optional[List[Union[str, Enum]]] = None, + dependencies: Optional[Sequence[Depends]] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + response_description: str = "Successful Response", + responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + deprecated: Optional[bool] = None, + methods: Optional[List[str]] = None, + operation_id: Optional[str] = None, + response_model_include: Optional[IncEx] = None, + response_model_exclude: Optional[IncEx] = None, + response_model_by_alias: bool = True, + response_model_exclude_unset: bool = False, + response_model_exclude_defaults: bool = False, + response_model_exclude_none: bool = False, + include_in_schema: bool = True, + response_class: Type[Response] = Default(JSONResponse), + name: Optional[str] = None, + openapi_extra: Optional[Dict[str, Any]] = None, + generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( + generate_unique_id + ), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.router.add_api_route( + path, + func, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=methods, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + return func + + return decorator + + def add_api_websocket_route( + self, + path: str, + endpoint: Callable[..., Any], + name: Optional[str] = None, + *, + dependencies: Optional[Sequence[Depends]] = None, + ) -> None: + self.router.add_api_websocket_route( + path, + endpoint, + name=name, + dependencies=dependencies, + ) + + def websocket( + self, + path: Annotated[ + str, + Doc( + """ + WebSocket path. + """ + ), + ], + name: Annotated[ + Optional[str], + Doc( + """ + A name for the WebSocket. Only used internally. + """ + ), + ] = None, + *, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be used for this + WebSocket. + + Read more about it in the + [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). + """ + ), + ] = None, + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Decorate a WebSocket function. + + Read more about it in the + [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). + + **Example** + + ```python + from fastapi import FastAPI, WebSocket + + app = FastAPI() + + @app.websocket("/ws") + async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text(f"Message text was: {data}") + ``` + """ + + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_api_websocket_route( + path, + func, + name=name, + dependencies=dependencies, + ) + return func + + return decorator + + def include_router( + self, + router: Annotated[routing.APIRouter, Doc("The `APIRouter` to include.")], + *, + prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to all the *path operations* in this + router. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to all the + *path operations* in this router. + + Read more about it in the + [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). + + **Example** + + ```python + from fastapi import Depends, FastAPI + + from .dependencies import get_token_header + from .internal import admin + + app = FastAPI() + + app.include_router( + admin.router, + dependencies=[Depends(get_token_header)], + ) + ``` + """ + ), + ] = None, + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses to be shown in OpenAPI. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/). + + And in the + [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark all the *path operations* in this router as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + **Example** + + ```python + from fastapi import FastAPI + + from .internal import old_api + + app = FastAPI() + + app.include_router( + old_api.router, + deprecated=True, + ) + ``` + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include (or not) all the *path operations* in this router in the + generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + **Example** + + ```python + from fastapi import FastAPI + + from .internal import old_api + + app = FastAPI() + + app.include_router( + old_api.router, + include_in_schema=False, + ) + ``` + """ + ), + ] = True, + default_response_class: Annotated[ + Type[Response], + Doc( + """ + Default response class to be used for the *path operations* in this + router. + + Read more in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). + + **Example** + + ```python + from fastapi import FastAPI + from fastapi.responses import ORJSONResponse + + from .internal import old_api + + app = FastAPI() + + app.include_router( + old_api.router, + default_response_class=ORJSONResponse, + ) + ``` + """ + ), + ] = Default(JSONResponse), + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> None: + """ + Include an `APIRouter` in the same app. + + Read more about it in the + [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/). + + ## Example + + ```python + from fastapi import FastAPI + + from .users import users_router + + app = FastAPI() + + app.include_router(users_router) + ``` + """ + self.router.include_router( + router, + prefix=prefix, + tags=tags, + dependencies=dependencies, + responses=responses, + deprecated=deprecated, + include_in_schema=include_in_schema, + default_response_class=default_response_class, + callbacks=callbacks, + generate_unique_id_function=generate_unique_id_function, + ) + + def get( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP GET operation. + + ## Example + + ```python + from fastapi import FastAPI + + app = FastAPI() + + @app.get("/items/") + def read_items(): + return [{"name": "Empanada"}, {"name": "Arepa"}] + ``` + """ + return self.router.get( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def put( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP PUT operation. + + ## Example + + ```python + from fastapi import FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + + @app.put("/items/{item_id}") + def replace_item(item_id: str, item: Item): + return {"message": "Item replaced", "id": item_id} + ``` + """ + return self.router.put( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def post( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP POST operation. + + ## Example + + ```python + from fastapi import FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + + @app.post("/items/") + def create_item(item: Item): + return {"message": "Item created"} + ``` + """ + return self.router.post( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def delete( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP DELETE operation. + + ## Example + + ```python + from fastapi import FastAPI + + app = FastAPI() + + @app.delete("/items/{item_id}") + def delete_item(item_id: str): + return {"message": "Item deleted"} + ``` + """ + return self.router.delete( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def options( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP OPTIONS operation. + + ## Example + + ```python + from fastapi import FastAPI + + app = FastAPI() + + @app.options("/items/") + def get_item_options(): + return {"additions": ["Aji", "Guacamole"]} + ``` + """ + return self.router.options( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def head( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP HEAD operation. + + ## Example + + ```python + from fastapi import FastAPI, Response + + app = FastAPI() + + @app.head("/items/", status_code=204) + def get_items_headers(response: Response): + response.headers["X-Cat-Dog"] = "Alone in the world" + ``` + """ + return self.router.head( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def patch( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP PATCH operation. + + ## Example + + ```python + from fastapi import FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + + @app.patch("/items/") + def update_item(item: Item): + return {"message": "Item updated in place"} + ``` + """ + return self.router.patch( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def trace( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[routing.APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP TRACE operation. + + ## Example + + ```python + from fastapi import FastAPI + + app = FastAPI() + + @app.put("/items/{item_id}") + def trace_item(item_id: str): + return None + ``` + """ + return self.router.trace( + path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def websocket_route( + self, path: str, name: Union[str, None] = None + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.router.add_websocket_route(path, func, name=name) + return func + + return decorator + + @deprecated( + """ + on_event is deprecated, use lifespan event handlers instead. + + Read more about it in the + [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/). + """ + ) + def on_event( + self, + event_type: Annotated[ + str, + Doc( + """ + The type of event. `startup` or `shutdown`. + """ + ), + ], + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add an event handler for the application. + + `on_event` is deprecated, use `lifespan` event handlers instead. + + Read more about it in the + [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated). + """ + return self.router.on_event(event_type) + + def middleware( + self, + middleware_type: Annotated[ + str, + Doc( + """ + The type of middleware. Currently only supports `http`. + """ + ), + ], + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a middleware to the application. + + Read more about it in the + [FastAPI docs for Middleware](https://fastapi.tiangolo.com/tutorial/middleware/). + + ## Example + + ```python + import time + + from fastapi import FastAPI, Request + + app = FastAPI() + + + @app.middleware("http") + async def add_process_time_header(request: Request, call_next): + start_time = time.time() + response = await call_next(request) + process_time = time.time() - start_time + response.headers["X-Process-Time"] = str(process_time) + return response + ``` + """ + + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_middleware(BaseHTTPMiddleware, dispatch=func) + return func + + return decorator + + def exception_handler( + self, + exc_class_or_status_code: Annotated[ + Union[int, Type[Exception]], + Doc( + """ + The Exception class this would handle, or a status code. + """ + ), + ], + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add an exception handler to the app. + + Read more about it in the + [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/). + + ## Example + + ```python + from fastapi import FastAPI, Request + from fastapi.responses import JSONResponse + + + class UnicornException(Exception): + def __init__(self, name: str): + self.name = name + + + app = FastAPI() + + + @app.exception_handler(UnicornException) + async def unicorn_exception_handler(request: Request, exc: UnicornException): + return JSONResponse( + status_code=418, + content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}, + ) + ``` + """ + + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_exception_handler(exc_class_or_status_code, func) + return func + + return decorator diff --git a/.venv/Lib/site-packages/fastapi/background.py b/.venv/Lib/site-packages/fastapi/background.py new file mode 100644 index 0000000..203578a --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/background.py @@ -0,0 +1,59 @@ +from typing import Any, Callable + +from starlette.background import BackgroundTasks as StarletteBackgroundTasks +from typing_extensions import Annotated, Doc, ParamSpec + +P = ParamSpec("P") + + +class BackgroundTasks(StarletteBackgroundTasks): + """ + A collection of background tasks that will be called after a response has been + sent to the client. + + Read more about it in the + [FastAPI docs for Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/). + + ## Example + + ```python + from fastapi import BackgroundTasks, FastAPI + + app = FastAPI() + + + def write_notification(email: str, message=""): + with open("log.txt", mode="w") as email_file: + content = f"notification for {email}: {message}" + email_file.write(content) + + + @app.post("/send-notification/{email}") + async def send_notification(email: str, background_tasks: BackgroundTasks): + background_tasks.add_task(write_notification, email, message="some notification") + return {"message": "Notification sent in the background"} + ``` + """ + + def add_task( + self, + func: Annotated[ + Callable[P, Any], + Doc( + """ + The function to call after the response is sent. + + It can be a regular `def` function or an `async def` function. + """ + ), + ], + *args: P.args, + **kwargs: P.kwargs, + ) -> None: + """ + Add a function to be called in the background after the response is sent. + + Read more about it in the + [FastAPI docs for Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/). + """ + return super().add_task(func, *args, **kwargs) diff --git a/.venv/Lib/site-packages/fastapi/cli.py b/.venv/Lib/site-packages/fastapi/cli.py new file mode 100644 index 0000000..8d3301e --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/cli.py @@ -0,0 +1,13 @@ +try: + from fastapi_cli.cli import main as cli_main + +except ImportError: # pragma: no cover + cli_main = None # type: ignore + + +def main() -> None: + if not cli_main: # type: ignore[truthy-function] + message = 'To use the fastapi command, please install "fastapi[standard]":\n\n\tpip install "fastapi[standard]"\n' + print(message) + raise RuntimeError(message) # noqa: B904 + cli_main() diff --git a/.venv/Lib/site-packages/fastapi/concurrency.py b/.venv/Lib/site-packages/fastapi/concurrency.py new file mode 100644 index 0000000..3202c70 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/concurrency.py @@ -0,0 +1,39 @@ +from contextlib import asynccontextmanager as asynccontextmanager +from typing import AsyncGenerator, ContextManager, TypeVar + +import anyio.to_thread +from anyio import CapacityLimiter +from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa +from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa +from starlette.concurrency import ( # noqa + run_until_first_complete as run_until_first_complete, +) + +_T = TypeVar("_T") + + +@asynccontextmanager +async def contextmanager_in_threadpool( + cm: ContextManager[_T], +) -> AsyncGenerator[_T, None]: + # blocking __exit__ from running waiting on a free thread + # can create race conditions/deadlocks if the context manager itself + # has its own internal pool (e.g. a database connection pool) + # to avoid this we let __exit__ run without a capacity limit + # since we're creating a new limiter for each call, any non-zero limit + # works (1 is arbitrary) + exit_limiter = CapacityLimiter(1) + try: + yield await run_in_threadpool(cm.__enter__) + except Exception as e: + ok = bool( + await anyio.to_thread.run_sync( + cm.__exit__, type(e), e, e.__traceback__, limiter=exit_limiter + ) + ) + if not ok: + raise e + else: + await anyio.to_thread.run_sync( + cm.__exit__, None, None, None, limiter=exit_limiter + ) diff --git a/.venv/Lib/site-packages/fastapi/datastructures.py b/.venv/Lib/site-packages/fastapi/datastructures.py new file mode 100644 index 0000000..cf8406b --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/datastructures.py @@ -0,0 +1,204 @@ +from typing import ( + Any, + BinaryIO, + Callable, + Dict, + Iterable, + Optional, + Type, + TypeVar, + cast, +) + +from fastapi._compat import ( + PYDANTIC_V2, + CoreSchema, + GetJsonSchemaHandler, + JsonSchemaValue, + with_info_plain_validator_function, +) +from starlette.datastructures import URL as URL # noqa: F401 +from starlette.datastructures import Address as Address # noqa: F401 +from starlette.datastructures import FormData as FormData # noqa: F401 +from starlette.datastructures import Headers as Headers # noqa: F401 +from starlette.datastructures import QueryParams as QueryParams # noqa: F401 +from starlette.datastructures import State as State # noqa: F401 +from starlette.datastructures import UploadFile as StarletteUploadFile +from typing_extensions import Annotated, Doc + + +class UploadFile(StarletteUploadFile): + """ + A file uploaded in a request. + + Define it as a *path operation function* (or dependency) parameter. + + If you are using a regular `def` function, you can use the `upload_file.file` + attribute to access the raw standard Python file (blocking, not async), useful and + needed for non-async code. + + Read more about it in the + [FastAPI docs for Request Files](https://fastapi.tiangolo.com/tutorial/request-files/). + + ## Example + + ```python + from typing import Annotated + + from fastapi import FastAPI, File, UploadFile + + app = FastAPI() + + + @app.post("/files/") + async def create_file(file: Annotated[bytes, File()]): + return {"file_size": len(file)} + + + @app.post("/uploadfile/") + async def create_upload_file(file: UploadFile): + return {"filename": file.filename} + ``` + """ + + file: Annotated[ + BinaryIO, + Doc("The standard Python file object (non-async)."), + ] + filename: Annotated[Optional[str], Doc("The original file name.")] + size: Annotated[Optional[int], Doc("The size of the file in bytes.")] + headers: Annotated[Headers, Doc("The headers of the request.")] + content_type: Annotated[ + Optional[str], Doc("The content type of the request, from the headers.") + ] + + async def write( + self, + data: Annotated[ + bytes, + Doc( + """ + The bytes to write to the file. + """ + ), + ], + ) -> None: + """ + Write some bytes to the file. + + You normally wouldn't use this from a file you read in a request. + + To be awaitable, compatible with async, this is run in threadpool. + """ + return await super().write(data) + + async def read( + self, + size: Annotated[ + int, + Doc( + """ + The number of bytes to read from the file. + """ + ), + ] = -1, + ) -> bytes: + """ + Read some bytes from the file. + + To be awaitable, compatible with async, this is run in threadpool. + """ + return await super().read(size) + + async def seek( + self, + offset: Annotated[ + int, + Doc( + """ + The position in bytes to seek to in the file. + """ + ), + ], + ) -> None: + """ + Move to a position in the file. + + Any next read or write will be done from that position. + + To be awaitable, compatible with async, this is run in threadpool. + """ + return await super().seek(offset) + + async def close(self) -> None: + """ + Close the file. + + To be awaitable, compatible with async, this is run in threadpool. + """ + return await super().close() + + @classmethod + def __get_validators__(cls: Type["UploadFile"]) -> Iterable[Callable[..., Any]]: + yield cls.validate + + @classmethod + def validate(cls: Type["UploadFile"], v: Any) -> Any: + if not isinstance(v, StarletteUploadFile): + raise ValueError(f"Expected UploadFile, received: {type(v)}") + return v + + @classmethod + def _validate(cls, __input_value: Any, _: Any) -> "UploadFile": + if not isinstance(__input_value, StarletteUploadFile): + raise ValueError(f"Expected UploadFile, received: {type(__input_value)}") + return cast(UploadFile, __input_value) + + if not PYDANTIC_V2: + + @classmethod + def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + field_schema.update({"type": "string", "format": "binary"}) + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + return {"type": "string", "format": "binary"} + + @classmethod + def __get_pydantic_core_schema__( + cls, source: Type[Any], handler: Callable[[Any], CoreSchema] + ) -> CoreSchema: + return with_info_plain_validator_function(cls._validate) + + +class DefaultPlaceholder: + """ + You shouldn't use this class directly. + + It's used internally to recognize when a default value has been overwritten, even + if the overridden default value was truthy. + """ + + def __init__(self, value: Any): + self.value = value + + def __bool__(self) -> bool: + return bool(self.value) + + def __eq__(self, o: object) -> bool: + return isinstance(o, DefaultPlaceholder) and o.value == self.value + + +DefaultType = TypeVar("DefaultType") + + +def Default(value: DefaultType) -> DefaultType: + """ + You shouldn't use this function directly. + + It's used internally to recognize when a default value has been overwritten, even + if the overridden default value was truthy. + """ + return DefaultPlaceholder(value) # type: ignore diff --git a/.venv/Lib/site-packages/fastapi/dependencies/__init__.py b/.venv/Lib/site-packages/fastapi/dependencies/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27900eb7ebab73887f7bcaba89456d4132511976 GIT binary patch literal 217 zcmX@j%ge<81Z$G5GC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!s&KZ72`x@7Dvk+G z%#CqLEzT~paZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%IiqyaT07G&zDq!y&+rKIL1XQmeG$H!;pWtPOp k>lIYq;;_lhPbtkwwJTx;I)o93i$RQ!%#4hTMa)1J0OSWe=>Px# literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d56f61d2a1d084e7ad5489b88f06ee70775b65a9 GIT binary patch literal 2760 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 T%bqkx#|2%*eH{YXLiYaxBl7L< literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/dependencies/models.py b/.venv/Lib/site-packages/fastapi/dependencies/models.py new file mode 100644 index 0000000..418c117 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/dependencies/models.py @@ -0,0 +1,37 @@ +from dataclasses import dataclass, field +from typing import Any, Callable, List, Optional, Sequence, Tuple + +from fastapi._compat import ModelField +from fastapi.security.base import SecurityBase + + +@dataclass +class SecurityRequirement: + security_scheme: SecurityBase + scopes: Optional[Sequence[str]] = None + + +@dataclass +class Dependant: + path_params: List[ModelField] = field(default_factory=list) + query_params: List[ModelField] = field(default_factory=list) + header_params: List[ModelField] = field(default_factory=list) + cookie_params: List[ModelField] = field(default_factory=list) + body_params: List[ModelField] = field(default_factory=list) + dependencies: List["Dependant"] = field(default_factory=list) + security_requirements: List[SecurityRequirement] = field(default_factory=list) + name: Optional[str] = None + call: Optional[Callable[..., Any]] = None + request_param_name: Optional[str] = None + websocket_param_name: Optional[str] = None + http_connection_param_name: Optional[str] = None + response_param_name: Optional[str] = None + background_tasks_param_name: Optional[str] = None + security_scopes_param_name: Optional[str] = None + security_scopes: Optional[List[str]] = None + use_cache: bool = True + path: Optional[str] = None + cache_key: Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] = field(init=False) + + def __post_init__(self) -> None: + self.cache_key = (self.call, tuple(sorted(set(self.security_scopes or [])))) diff --git a/.venv/Lib/site-packages/fastapi/dependencies/utils.py b/.venv/Lib/site-packages/fastapi/dependencies/utils.py new file mode 100644 index 0000000..84dfa4d --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/dependencies/utils.py @@ -0,0 +1,980 @@ +import inspect +from contextlib import AsyncExitStack, contextmanager +from copy import copy, deepcopy +from dataclasses import dataclass +from typing import ( + Any, + Callable, + Coroutine, + Dict, + ForwardRef, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) + +import anyio +from fastapi import params +from fastapi._compat import ( + PYDANTIC_V2, + ErrorWrapper, + ModelField, + RequiredParam, + Undefined, + _regenerate_error_with_loc, + copy_field_info, + create_body_model, + evaluate_forwardref, + field_annotation_is_scalar, + get_annotation_from_field_info, + get_cached_model_fields, + get_missing_field_error, + is_bytes_field, + is_bytes_sequence_field, + is_scalar_field, + is_scalar_sequence_field, + is_sequence_field, + is_uploadfile_or_nonable_uploadfile_annotation, + is_uploadfile_sequence_annotation, + lenient_issubclass, + sequence_types, + serialize_sequence_value, + value_is_sequence, +) +from fastapi.background import BackgroundTasks +from fastapi.concurrency import ( + asynccontextmanager, + contextmanager_in_threadpool, +) +from fastapi.dependencies.models import Dependant, SecurityRequirement +from fastapi.logger import logger +from fastapi.security.base import SecurityBase +from fastapi.security.oauth2 import OAuth2, SecurityScopes +from fastapi.security.open_id_connect_url import OpenIdConnect +from fastapi.utils import create_model_field, get_path_param_names +from pydantic import BaseModel +from pydantic.fields import FieldInfo +from starlette.background import BackgroundTasks as StarletteBackgroundTasks +from starlette.concurrency import run_in_threadpool +from starlette.datastructures import ( + FormData, + Headers, + ImmutableMultiDict, + QueryParams, + UploadFile, +) +from starlette.requests import HTTPConnection, Request +from starlette.responses import Response +from starlette.websockets import WebSocket +from typing_extensions import Annotated, get_args, get_origin + +multipart_not_installed_error = ( + 'Form data requires "python-multipart" to be installed. \n' + 'You can install "python-multipart" with: \n\n' + "pip install python-multipart\n" +) +multipart_incorrect_install_error = ( + 'Form data requires "python-multipart" to be installed. ' + 'It seems you installed "multipart" instead. \n' + 'You can remove "multipart" with: \n\n' + "pip uninstall multipart\n\n" + 'And then install "python-multipart" with: \n\n' + "pip install python-multipart\n" +) + + +def ensure_multipart_is_installed() -> None: + try: + from python_multipart import __version__ + + # Import an attribute that can be mocked/deleted in testing + assert __version__ > "0.0.12" + except (ImportError, AssertionError): + try: + # __version__ is available in both multiparts, and can be mocked + from multipart import __version__ # type: ignore[no-redef,import-untyped] + + assert __version__ + try: + # parse_options_header is only available in the right multipart + from multipart.multipart import ( # type: ignore[import-untyped] + parse_options_header, + ) + + assert parse_options_header + except ImportError: + logger.error(multipart_incorrect_install_error) + raise RuntimeError(multipart_incorrect_install_error) from None + except ImportError: + logger.error(multipart_not_installed_error) + raise RuntimeError(multipart_not_installed_error) from None + + +def get_param_sub_dependant( + *, + param_name: str, + depends: params.Depends, + path: str, + security_scopes: Optional[List[str]] = None, +) -> Dependant: + assert depends.dependency + return get_sub_dependant( + depends=depends, + dependency=depends.dependency, + path=path, + name=param_name, + security_scopes=security_scopes, + ) + + +def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant: + assert callable(depends.dependency), ( + "A parameter-less dependency must have a callable dependency" + ) + return get_sub_dependant(depends=depends, dependency=depends.dependency, path=path) + + +def get_sub_dependant( + *, + depends: params.Depends, + dependency: Callable[..., Any], + path: str, + name: Optional[str] = None, + security_scopes: Optional[List[str]] = None, +) -> Dependant: + security_requirement = None + security_scopes = security_scopes or [] + if isinstance(depends, params.Security): + dependency_scopes = depends.scopes + security_scopes.extend(dependency_scopes) + if isinstance(dependency, SecurityBase): + use_scopes: List[str] = [] + if isinstance(dependency, (OAuth2, OpenIdConnect)): + use_scopes = security_scopes + security_requirement = SecurityRequirement( + security_scheme=dependency, scopes=use_scopes + ) + sub_dependant = get_dependant( + path=path, + call=dependency, + name=name, + security_scopes=security_scopes, + use_cache=depends.use_cache, + ) + if security_requirement: + sub_dependant.security_requirements.append(security_requirement) + return sub_dependant + + +CacheKey = Tuple[Optional[Callable[..., Any]], Tuple[str, ...]] + + +def get_flat_dependant( + dependant: Dependant, + *, + skip_repeats: bool = False, + visited: Optional[List[CacheKey]] = None, +) -> Dependant: + if visited is None: + visited = [] + visited.append(dependant.cache_key) + + flat_dependant = Dependant( + path_params=dependant.path_params.copy(), + query_params=dependant.query_params.copy(), + header_params=dependant.header_params.copy(), + cookie_params=dependant.cookie_params.copy(), + body_params=dependant.body_params.copy(), + security_requirements=dependant.security_requirements.copy(), + use_cache=dependant.use_cache, + path=dependant.path, + ) + for sub_dependant in dependant.dependencies: + if skip_repeats and sub_dependant.cache_key in visited: + continue + flat_sub = get_flat_dependant( + sub_dependant, skip_repeats=skip_repeats, visited=visited + ) + flat_dependant.path_params.extend(flat_sub.path_params) + flat_dependant.query_params.extend(flat_sub.query_params) + flat_dependant.header_params.extend(flat_sub.header_params) + flat_dependant.cookie_params.extend(flat_sub.cookie_params) + flat_dependant.body_params.extend(flat_sub.body_params) + flat_dependant.security_requirements.extend(flat_sub.security_requirements) + return flat_dependant + + +def _get_flat_fields_from_params(fields: List[ModelField]) -> List[ModelField]: + if not fields: + return fields + first_field = fields[0] + if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): + fields_to_extract = get_cached_model_fields(first_field.type_) + return fields_to_extract + return fields + + +def get_flat_params(dependant: Dependant) -> List[ModelField]: + flat_dependant = get_flat_dependant(dependant, skip_repeats=True) + path_params = _get_flat_fields_from_params(flat_dependant.path_params) + query_params = _get_flat_fields_from_params(flat_dependant.query_params) + header_params = _get_flat_fields_from_params(flat_dependant.header_params) + cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params) + return path_params + query_params + header_params + cookie_params + + +def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: + signature = inspect.signature(call) + globalns = getattr(call, "__globals__", {}) + typed_params = [ + inspect.Parameter( + name=param.name, + kind=param.kind, + default=param.default, + annotation=get_typed_annotation(param.annotation, globalns), + ) + for param in signature.parameters.values() + ] + typed_signature = inspect.Signature(typed_params) + return typed_signature + + +def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any: + if isinstance(annotation, str): + annotation = ForwardRef(annotation) + annotation = evaluate_forwardref(annotation, globalns, globalns) + return annotation + + +def get_typed_return_annotation(call: Callable[..., Any]) -> Any: + signature = inspect.signature(call) + annotation = signature.return_annotation + + if annotation is inspect.Signature.empty: + return None + + globalns = getattr(call, "__globals__", {}) + return get_typed_annotation(annotation, globalns) + + +def get_dependant( + *, + path: str, + call: Callable[..., Any], + name: Optional[str] = None, + security_scopes: Optional[List[str]] = None, + use_cache: bool = True, +) -> Dependant: + path_param_names = get_path_param_names(path) + endpoint_signature = get_typed_signature(call) + signature_params = endpoint_signature.parameters + dependant = Dependant( + call=call, + name=name, + path=path, + security_scopes=security_scopes, + use_cache=use_cache, + ) + for param_name, param in signature_params.items(): + is_path_param = param_name in path_param_names + param_details = analyze_param( + param_name=param_name, + annotation=param.annotation, + value=param.default, + is_path_param=is_path_param, + ) + if param_details.depends is not None: + sub_dependant = get_param_sub_dependant( + param_name=param_name, + depends=param_details.depends, + path=path, + security_scopes=security_scopes, + ) + dependant.dependencies.append(sub_dependant) + continue + if add_non_field_param_to_dependency( + param_name=param_name, + type_annotation=param_details.type_annotation, + dependant=dependant, + ): + assert param_details.field is None, ( + f"Cannot specify multiple FastAPI annotations for {param_name!r}" + ) + continue + assert param_details.field is not None + if isinstance(param_details.field.field_info, params.Body): + dependant.body_params.append(param_details.field) + else: + add_param_to_fields(field=param_details.field, dependant=dependant) + return dependant + + +def add_non_field_param_to_dependency( + *, param_name: str, type_annotation: Any, dependant: Dependant +) -> Optional[bool]: + if lenient_issubclass(type_annotation, Request): + dependant.request_param_name = param_name + return True + elif lenient_issubclass(type_annotation, WebSocket): + dependant.websocket_param_name = param_name + return True + elif lenient_issubclass(type_annotation, HTTPConnection): + dependant.http_connection_param_name = param_name + return True + elif lenient_issubclass(type_annotation, Response): + dependant.response_param_name = param_name + return True + elif lenient_issubclass(type_annotation, StarletteBackgroundTasks): + dependant.background_tasks_param_name = param_name + return True + elif lenient_issubclass(type_annotation, SecurityScopes): + dependant.security_scopes_param_name = param_name + return True + return None + + +@dataclass +class ParamDetails: + type_annotation: Any + depends: Optional[params.Depends] + field: Optional[ModelField] + + +def analyze_param( + *, + param_name: str, + annotation: Any, + value: Any, + is_path_param: bool, +) -> ParamDetails: + field_info = None + depends = None + type_annotation: Any = Any + use_annotation: Any = Any + if annotation is not inspect.Signature.empty: + use_annotation = annotation + type_annotation = annotation + # Extract Annotated info + if get_origin(use_annotation) is Annotated: + annotated_args = get_args(annotation) + type_annotation = annotated_args[0] + fastapi_annotations = [ + arg + for arg in annotated_args[1:] + if isinstance(arg, (FieldInfo, params.Depends)) + ] + fastapi_specific_annotations = [ + arg + for arg in fastapi_annotations + if isinstance(arg, (params.Param, params.Body, params.Depends)) + ] + if fastapi_specific_annotations: + fastapi_annotation: Union[FieldInfo, params.Depends, None] = ( + fastapi_specific_annotations[-1] + ) + else: + fastapi_annotation = None + # Set default for Annotated FieldInfo + if isinstance(fastapi_annotation, FieldInfo): + # Copy `field_info` because we mutate `field_info.default` below. + field_info = copy_field_info( + field_info=fastapi_annotation, annotation=use_annotation + ) + assert ( + field_info.default is Undefined or field_info.default is RequiredParam + ), ( + f"`{field_info.__class__.__name__}` default value cannot be set in" + f" `Annotated` for {param_name!r}. Set the default value with `=` instead." + ) + if value is not inspect.Signature.empty: + assert not is_path_param, "Path parameters cannot have default values" + field_info.default = value + else: + field_info.default = RequiredParam + # Get Annotated Depends + elif isinstance(fastapi_annotation, params.Depends): + depends = fastapi_annotation + # Get Depends from default value + if isinstance(value, params.Depends): + assert depends is None, ( + "Cannot specify `Depends` in `Annotated` and default value" + f" together for {param_name!r}" + ) + assert field_info is None, ( + "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a" + f" default value together for {param_name!r}" + ) + depends = value + # Get FieldInfo from default value + elif isinstance(value, FieldInfo): + assert field_info is None, ( + "Cannot specify FastAPI annotations in `Annotated` and default value" + f" together for {param_name!r}" + ) + field_info = value + if PYDANTIC_V2: + field_info.annotation = type_annotation + + # Get Depends from type annotation + if depends is not None and depends.dependency is None: + # Copy `depends` before mutating it + depends = copy(depends) + depends.dependency = type_annotation + + # Handle non-param type annotations like Request + if lenient_issubclass( + type_annotation, + ( + Request, + WebSocket, + HTTPConnection, + Response, + StarletteBackgroundTasks, + SecurityScopes, + ), + ): + assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}" + assert field_info is None, ( + f"Cannot specify FastAPI annotation for type {type_annotation!r}" + ) + # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value + elif field_info is None and depends is None: + default_value = value if value is not inspect.Signature.empty else RequiredParam + if is_path_param: + # We might check here that `default_value is RequiredParam`, but the fact is that the same + # parameter might sometimes be a path parameter and sometimes not. See + # `tests/test_infer_param_optionality.py` for an example. + field_info = params.Path(annotation=use_annotation) + elif is_uploadfile_or_nonable_uploadfile_annotation( + type_annotation + ) or is_uploadfile_sequence_annotation(type_annotation): + field_info = params.File(annotation=use_annotation, default=default_value) + elif not field_annotation_is_scalar(annotation=type_annotation): + field_info = params.Body(annotation=use_annotation, default=default_value) + else: + field_info = params.Query(annotation=use_annotation, default=default_value) + + field = None + # It's a field_info, not a dependency + if field_info is not None: + # Handle field_info.in_ + if is_path_param: + assert isinstance(field_info, params.Path), ( + f"Cannot use `{field_info.__class__.__name__}` for path param" + f" {param_name!r}" + ) + elif ( + isinstance(field_info, params.Param) + and getattr(field_info, "in_", None) is None + ): + field_info.in_ = params.ParamTypes.query + use_annotation_from_field_info = get_annotation_from_field_info( + use_annotation, + field_info, + param_name, + ) + if isinstance(field_info, params.Form): + ensure_multipart_is_installed() + if not field_info.alias and getattr(field_info, "convert_underscores", None): + alias = param_name.replace("_", "-") + else: + alias = field_info.alias or param_name + field_info.alias = alias + field = create_model_field( + name=param_name, + type_=use_annotation_from_field_info, + default=field_info.default, + alias=alias, + required=field_info.default in (RequiredParam, Undefined), + field_info=field_info, + ) + if is_path_param: + assert is_scalar_field(field=field), ( + "Path params must be of one of the supported types" + ) + elif isinstance(field_info, params.Query): + assert ( + is_scalar_field(field) + or is_scalar_sequence_field(field) + or ( + lenient_issubclass(field.type_, BaseModel) + # For Pydantic v1 + and getattr(field, "shape", 1) == 1 + ) + ) + + return ParamDetails(type_annotation=type_annotation, depends=depends, field=field) + + +def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None: + field_info = field.field_info + field_info_in = getattr(field_info, "in_", None) + if field_info_in == params.ParamTypes.path: + dependant.path_params.append(field) + elif field_info_in == params.ParamTypes.query: + dependant.query_params.append(field) + elif field_info_in == params.ParamTypes.header: + dependant.header_params.append(field) + else: + assert field_info_in == params.ParamTypes.cookie, ( + f"non-body parameters must be in path, query, header or cookie: {field.name}" + ) + dependant.cookie_params.append(field) + + +def is_coroutine_callable(call: Callable[..., Any]) -> bool: + if inspect.isroutine(call): + return inspect.iscoroutinefunction(call) + if inspect.isclass(call): + return False + dunder_call = getattr(call, "__call__", None) # noqa: B004 + return inspect.iscoroutinefunction(dunder_call) + + +def is_async_gen_callable(call: Callable[..., Any]) -> bool: + if inspect.isasyncgenfunction(call): + return True + dunder_call = getattr(call, "__call__", None) # noqa: B004 + return inspect.isasyncgenfunction(dunder_call) + + +def is_gen_callable(call: Callable[..., Any]) -> bool: + if inspect.isgeneratorfunction(call): + return True + dunder_call = getattr(call, "__call__", None) # noqa: B004 + return inspect.isgeneratorfunction(dunder_call) + + +async def solve_generator( + *, call: Callable[..., Any], stack: AsyncExitStack, sub_values: Dict[str, Any] +) -> Any: + if is_gen_callable(call): + cm = contextmanager_in_threadpool(contextmanager(call)(**sub_values)) + elif is_async_gen_callable(call): + cm = asynccontextmanager(call)(**sub_values) + return await stack.enter_async_context(cm) + + +@dataclass +class SolvedDependency: + values: Dict[str, Any] + errors: List[Any] + background_tasks: Optional[StarletteBackgroundTasks] + response: Response + dependency_cache: Dict[Tuple[Callable[..., Any], Tuple[str]], Any] + + +async def solve_dependencies( + *, + request: Union[Request, WebSocket], + dependant: Dependant, + body: Optional[Union[Dict[str, Any], FormData]] = None, + background_tasks: Optional[StarletteBackgroundTasks] = None, + response: Optional[Response] = None, + dependency_overrides_provider: Optional[Any] = None, + dependency_cache: Optional[Dict[Tuple[Callable[..., Any], Tuple[str]], Any]] = None, + async_exit_stack: AsyncExitStack, + embed_body_fields: bool, +) -> SolvedDependency: + values: Dict[str, Any] = {} + errors: List[Any] = [] + if response is None: + response = Response() + del response.headers["content-length"] + response.status_code = None # type: ignore + dependency_cache = dependency_cache or {} + sub_dependant: Dependant + for sub_dependant in dependant.dependencies: + sub_dependant.call = cast(Callable[..., Any], sub_dependant.call) + sub_dependant.cache_key = cast( + Tuple[Callable[..., Any], Tuple[str]], sub_dependant.cache_key + ) + call = sub_dependant.call + use_sub_dependant = sub_dependant + if ( + dependency_overrides_provider + and dependency_overrides_provider.dependency_overrides + ): + original_call = sub_dependant.call + call = getattr( + dependency_overrides_provider, "dependency_overrides", {} + ).get(original_call, original_call) + use_path: str = sub_dependant.path # type: ignore + use_sub_dependant = get_dependant( + path=use_path, + call=call, + name=sub_dependant.name, + security_scopes=sub_dependant.security_scopes, + ) + + solved_result = await solve_dependencies( + request=request, + dependant=use_sub_dependant, + body=body, + background_tasks=background_tasks, + response=response, + dependency_overrides_provider=dependency_overrides_provider, + dependency_cache=dependency_cache, + async_exit_stack=async_exit_stack, + embed_body_fields=embed_body_fields, + ) + background_tasks = solved_result.background_tasks + dependency_cache.update(solved_result.dependency_cache) + if solved_result.errors: + errors.extend(solved_result.errors) + continue + if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache: + solved = dependency_cache[sub_dependant.cache_key] + elif is_gen_callable(call) or is_async_gen_callable(call): + solved = await solve_generator( + call=call, stack=async_exit_stack, sub_values=solved_result.values + ) + elif is_coroutine_callable(call): + solved = await call(**solved_result.values) + else: + solved = await run_in_threadpool(call, **solved_result.values) + if sub_dependant.name is not None: + values[sub_dependant.name] = solved + if sub_dependant.cache_key not in dependency_cache: + dependency_cache[sub_dependant.cache_key] = solved + path_values, path_errors = request_params_to_args( + dependant.path_params, request.path_params + ) + query_values, query_errors = request_params_to_args( + dependant.query_params, request.query_params + ) + header_values, header_errors = request_params_to_args( + dependant.header_params, request.headers + ) + cookie_values, cookie_errors = request_params_to_args( + dependant.cookie_params, request.cookies + ) + values.update(path_values) + values.update(query_values) + values.update(header_values) + values.update(cookie_values) + errors += path_errors + query_errors + header_errors + cookie_errors + if dependant.body_params: + ( + body_values, + body_errors, + ) = await request_body_to_args( # body_params checked above + body_fields=dependant.body_params, + received_body=body, + embed_body_fields=embed_body_fields, + ) + values.update(body_values) + errors.extend(body_errors) + if dependant.http_connection_param_name: + values[dependant.http_connection_param_name] = request + if dependant.request_param_name and isinstance(request, Request): + values[dependant.request_param_name] = request + elif dependant.websocket_param_name and isinstance(request, WebSocket): + values[dependant.websocket_param_name] = request + if dependant.background_tasks_param_name: + if background_tasks is None: + background_tasks = BackgroundTasks() + values[dependant.background_tasks_param_name] = background_tasks + if dependant.response_param_name: + values[dependant.response_param_name] = response + if dependant.security_scopes_param_name: + values[dependant.security_scopes_param_name] = SecurityScopes( + scopes=dependant.security_scopes + ) + return SolvedDependency( + values=values, + errors=errors, + background_tasks=background_tasks, + response=response, + dependency_cache=dependency_cache, + ) + + +def _validate_value_with_model_field( + *, field: ModelField, value: Any, values: Dict[str, Any], loc: Tuple[str, ...] +) -> Tuple[Any, List[Any]]: + if value is None: + if field.required: + return None, [get_missing_field_error(loc=loc)] + else: + return deepcopy(field.default), [] + v_, errors_ = field.validate(value, values, loc=loc) + if isinstance(errors_, ErrorWrapper): + return None, [errors_] + elif isinstance(errors_, list): + new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=()) + return None, new_errors + else: + return v_, [] + + +def _get_multidict_value( + field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None +) -> Any: + alias = alias or field.alias + if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)): + value = values.getlist(alias) + else: + value = values.get(alias, None) + if ( + value is None + or ( + isinstance(field.field_info, params.Form) + and isinstance(value, str) # For type checks + and value == "" + ) + or (is_sequence_field(field) and len(value) == 0) + ): + if field.required: + return + else: + return deepcopy(field.default) + return value + + +def request_params_to_args( + fields: Sequence[ModelField], + received_params: Union[Mapping[str, Any], QueryParams, Headers], +) -> Tuple[Dict[str, Any], List[Any]]: + values: Dict[str, Any] = {} + errors: List[Dict[str, Any]] = [] + + if not fields: + return values, errors + + first_field = fields[0] + fields_to_extract = fields + single_not_embedded_field = False + default_convert_underscores = True + if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): + fields_to_extract = get_cached_model_fields(first_field.type_) + single_not_embedded_field = True + # If headers are in a Pydantic model, the way to disable convert_underscores + # would be with Header(convert_underscores=False) at the Pydantic model level + default_convert_underscores = getattr( + first_field.field_info, "convert_underscores", True + ) + + params_to_process: Dict[str, Any] = {} + + processed_keys = set() + + for field in fields_to_extract: + alias = None + if isinstance(received_params, Headers): + # Handle fields extracted from a Pydantic Model for a header, each field + # doesn't have a FieldInfo of type Header with the default convert_underscores=True + convert_underscores = getattr( + field.field_info, "convert_underscores", default_convert_underscores + ) + if convert_underscores: + alias = ( + field.alias + if field.alias != field.name + else field.name.replace("_", "-") + ) + value = _get_multidict_value(field, received_params, alias=alias) + if value is not None: + params_to_process[field.name] = value + processed_keys.add(alias or field.alias) + processed_keys.add(field.name) + + for key, value in received_params.items(): + if key not in processed_keys: + params_to_process[key] = value + + if single_not_embedded_field: + field_info = first_field.field_info + assert isinstance(field_info, params.Param), ( + "Params must be subclasses of Param" + ) + loc: Tuple[str, ...] = (field_info.in_.value,) + v_, errors_ = _validate_value_with_model_field( + field=first_field, value=params_to_process, values=values, loc=loc + ) + return {first_field.name: v_}, errors_ + + for field in fields: + value = _get_multidict_value(field, received_params) + field_info = field.field_info + assert isinstance(field_info, params.Param), ( + "Params must be subclasses of Param" + ) + loc = (field_info.in_.value, field.alias) + v_, errors_ = _validate_value_with_model_field( + field=field, value=value, values=values, loc=loc + ) + if errors_: + errors.extend(errors_) + else: + values[field.name] = v_ + return values, errors + + +def _should_embed_body_fields(fields: List[ModelField]) -> bool: + if not fields: + return False + # More than one dependency could have the same field, it would show up as multiple + # fields but it's the same one, so count them by name + body_param_names_set = {field.name for field in fields} + # A top level field has to be a single field, not multiple + if len(body_param_names_set) > 1: + return True + first_field = fields[0] + # If it explicitly specifies it is embedded, it has to be embedded + if getattr(first_field.field_info, "embed", None): + return True + # If it's a Form (or File) field, it has to be a BaseModel to be top level + # otherwise it has to be embedded, so that the key value pair can be extracted + if isinstance(first_field.field_info, params.Form) and not lenient_issubclass( + first_field.type_, BaseModel + ): + return True + return False + + +async def _extract_form_body( + body_fields: List[ModelField], + received_body: FormData, +) -> Dict[str, Any]: + values = {} + first_field = body_fields[0] + first_field_info = first_field.field_info + + for field in body_fields: + value = _get_multidict_value(field, received_body) + if ( + isinstance(first_field_info, params.File) + and is_bytes_field(field) + and isinstance(value, UploadFile) + ): + value = await value.read() + elif ( + is_bytes_sequence_field(field) + and isinstance(first_field_info, params.File) + and value_is_sequence(value) + ): + # For types + assert isinstance(value, sequence_types) # type: ignore[arg-type] + results: List[Union[bytes, str]] = [] + + async def process_fn( + fn: Callable[[], Coroutine[Any, Any, Any]], + ) -> None: + result = await fn() + results.append(result) # noqa: B023 + + async with anyio.create_task_group() as tg: + for sub_value in value: + tg.start_soon(process_fn, sub_value.read) + value = serialize_sequence_value(field=field, value=results) + if value is not None: + values[field.alias] = value + for key, value in received_body.items(): + if key not in values: + values[key] = value + return values + + +async def request_body_to_args( + body_fields: List[ModelField], + received_body: Optional[Union[Dict[str, Any], FormData]], + embed_body_fields: bool, +) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: + values: Dict[str, Any] = {} + errors: List[Dict[str, Any]] = [] + assert body_fields, "request_body_to_args() should be called with fields" + single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields + first_field = body_fields[0] + body_to_process = received_body + + fields_to_extract: List[ModelField] = body_fields + + if single_not_embedded_field and lenient_issubclass(first_field.type_, BaseModel): + fields_to_extract = get_cached_model_fields(first_field.type_) + + if isinstance(received_body, FormData): + body_to_process = await _extract_form_body(fields_to_extract, received_body) + + if single_not_embedded_field: + loc: Tuple[str, ...] = ("body",) + v_, errors_ = _validate_value_with_model_field( + field=first_field, value=body_to_process, values=values, loc=loc + ) + return {first_field.name: v_}, errors_ + for field in body_fields: + loc = ("body", field.alias) + value: Optional[Any] = None + if body_to_process is not None: + try: + value = body_to_process.get(field.alias) + # If the received body is a list, not a dict + except AttributeError: + errors.append(get_missing_field_error(loc)) + continue + v_, errors_ = _validate_value_with_model_field( + field=field, value=value, values=values, loc=loc + ) + if errors_: + errors.extend(errors_) + else: + values[field.name] = v_ + return values, errors + + +def get_body_field( + *, flat_dependant: Dependant, name: str, embed_body_fields: bool +) -> Optional[ModelField]: + """ + Get a ModelField representing the request body for a path operation, combining + all body parameters into a single field if necessary. + + Used to check if it's form data (with `isinstance(body_field, params.Form)`) + or JSON and to generate the JSON Schema for a request body. + + This is **not** used to validate/parse the request body, that's done with each + individual body parameter. + """ + if not flat_dependant.body_params: + return None + first_param = flat_dependant.body_params[0] + if not embed_body_fields: + return first_param + model_name = "Body_" + name + BodyModel = create_body_model( + fields=flat_dependant.body_params, model_name=model_name + ) + required = any(True for f in flat_dependant.body_params if f.required) + BodyFieldInfo_kwargs: Dict[str, Any] = { + "annotation": BodyModel, + "alias": "body", + } + if not required: + BodyFieldInfo_kwargs["default"] = None + if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params): + BodyFieldInfo: Type[params.Body] = params.File + elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params): + BodyFieldInfo = params.Form + else: + BodyFieldInfo = params.Body + + body_param_media_types = [ + f.field_info.media_type + for f in flat_dependant.body_params + if isinstance(f.field_info, params.Body) + ] + if len(set(body_param_media_types)) == 1: + BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0] + final_field = create_model_field( + name="body", + type_=BodyModel, + required=required, + alias="body", + field_info=BodyFieldInfo(**BodyFieldInfo_kwargs), + ) + return final_field diff --git a/.venv/Lib/site-packages/fastapi/encoders.py b/.venv/Lib/site-packages/fastapi/encoders.py new file mode 100644 index 0000000..451ea07 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/encoders.py @@ -0,0 +1,343 @@ +import dataclasses +import datetime +from collections import defaultdict, deque +from decimal import Decimal +from enum import Enum +from ipaddress import ( + IPv4Address, + IPv4Interface, + IPv4Network, + IPv6Address, + IPv6Interface, + IPv6Network, +) +from pathlib import Path, PurePath +from re import Pattern +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from uuid import UUID + +from fastapi.types import IncEx +from pydantic import BaseModel +from pydantic.color import Color +from pydantic.networks import AnyUrl, NameEmail +from pydantic.types import SecretBytes, SecretStr +from typing_extensions import Annotated, Doc + +from ._compat import PYDANTIC_V2, UndefinedType, Url, _model_dump + + +# Taken from Pydantic v1 as is +def isoformat(o: Union[datetime.date, datetime.time]) -> str: + return o.isoformat() + + +# Taken from Pydantic v1 as is +# TODO: pv2 should this return strings instead? +def decimal_encoder(dec_value: Decimal) -> Union[int, float]: + """ + Encodes a Decimal as int of there's no exponent, otherwise float + + This is useful when we use ConstrainedDecimal to represent Numeric(x,0) + where a integer (but not int typed) is used. Encoding this as a float + results in failed round-tripping between encode and parse. + Our Id type is a prime example of this. + + >>> decimal_encoder(Decimal("1.0")) + 1.0 + + >>> decimal_encoder(Decimal("1")) + 1 + """ + if dec_value.as_tuple().exponent >= 0: # type: ignore[operator] + return int(dec_value) + else: + return float(dec_value) + + +ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { + bytes: lambda o: o.decode(), + Color: str, + datetime.date: isoformat, + datetime.datetime: isoformat, + datetime.time: isoformat, + datetime.timedelta: lambda td: td.total_seconds(), + Decimal: decimal_encoder, + Enum: lambda o: o.value, + frozenset: list, + deque: list, + GeneratorType: list, + IPv4Address: str, + IPv4Interface: str, + IPv4Network: str, + IPv6Address: str, + IPv6Interface: str, + IPv6Network: str, + NameEmail: str, + Path: str, + Pattern: lambda o: o.pattern, + SecretBytes: str, + SecretStr: str, + set: list, + UUID: str, + Url: str, + AnyUrl: str, +} + + +def generate_encoders_by_class_tuples( + type_encoder_map: Dict[Any, Callable[[Any], Any]], +) -> Dict[Callable[[Any], Any], Tuple[Any, ...]]: + encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict( + tuple + ) + for type_, encoder in type_encoder_map.items(): + encoders_by_class_tuples[encoder] += (type_,) + return encoders_by_class_tuples + + +encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE) + + +def jsonable_encoder( + obj: Annotated[ + Any, + Doc( + """ + The input object to convert to JSON. + """ + ), + ], + include: Annotated[ + Optional[IncEx], + Doc( + """ + Pydantic's `include` parameter, passed to Pydantic models to set the + fields to include. + """ + ), + ] = None, + exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Pydantic's `exclude` parameter, passed to Pydantic models to set the + fields to exclude. + """ + ), + ] = None, + by_alias: Annotated[ + bool, + Doc( + """ + Pydantic's `by_alias` parameter, passed to Pydantic models to define if + the output should use the alias names (when provided) or the Python + attribute names. In an API, if you set an alias, it's probably because you + want to use it in the result, so you probably want to leave this set to + `True`. + """ + ), + ] = True, + exclude_unset: Annotated[ + bool, + Doc( + """ + Pydantic's `exclude_unset` parameter, passed to Pydantic models to define + if it should exclude from the output the fields that were not explicitly + set (and that only had their default values). + """ + ), + ] = False, + exclude_defaults: Annotated[ + bool, + Doc( + """ + Pydantic's `exclude_defaults` parameter, passed to Pydantic models to define + if it should exclude from the output the fields that had the same default + value, even when they were explicitly set. + """ + ), + ] = False, + exclude_none: Annotated[ + bool, + Doc( + """ + Pydantic's `exclude_none` parameter, passed to Pydantic models to define + if it should exclude from the output any fields that have a `None` value. + """ + ), + ] = False, + custom_encoder: Annotated[ + Optional[Dict[Any, Callable[[Any], Any]]], + Doc( + """ + Pydantic's `custom_encoder` parameter, passed to Pydantic models to define + a custom encoder. + """ + ), + ] = None, + sqlalchemy_safe: Annotated[ + bool, + Doc( + """ + Exclude from the output any fields that start with the name `_sa`. + + This is mainly a hack for compatibility with SQLAlchemy objects, they + store internal SQLAlchemy-specific state in attributes named with `_sa`, + and those objects can't (and shouldn't be) serialized to JSON. + """ + ), + ] = True, +) -> Any: + """ + Convert any object to something that can be encoded in JSON. + + This is used internally by FastAPI to make sure anything you return can be + encoded as JSON before it is sent to the client. + + You can also use it yourself, for example to convert objects before saving them + in a database that supports only JSON. + + Read more about it in the + [FastAPI docs for JSON Compatible Encoder](https://fastapi.tiangolo.com/tutorial/encoder/). + """ + custom_encoder = custom_encoder or {} + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if include is not None and not isinstance(include, (set, dict)): + include = set(include) + if exclude is not None and not isinstance(exclude, (set, dict)): + exclude = set(exclude) + if isinstance(obj, BaseModel): + # TODO: remove when deprecating Pydantic v1 + encoders: Dict[Any, Any] = {} + if not PYDANTIC_V2: + encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined] + if custom_encoder: + encoders.update(custom_encoder) + obj_dict = _model_dump( + obj, + mode="json", + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_none=exclude_none, + exclude_defaults=exclude_defaults, + ) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + return jsonable_encoder( + obj_dict, + exclude_none=exclude_none, + exclude_defaults=exclude_defaults, + # TODO: remove when deprecating Pydantic v1 + custom_encoder=encoders, + sqlalchemy_safe=sqlalchemy_safe, + ) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) + return jsonable_encoder( + obj_dict, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + custom_encoder=custom_encoder, + sqlalchemy_safe=sqlalchemy_safe, + ) + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, UndefinedType): + return None + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + if include is not None: + allowed_keys &= set(include) + if exclude is not None: + allowed_keys -= set(exclude) + for key, value in obj.items(): + if ( + ( + not sqlalchemy_safe + or (not isinstance(key, str)) + or (not key.startswith("_sa")) + ) + and (value is not None or not exclude_none) + and key in allowed_keys + ): + encoded_key = jsonable_encoder( + key, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_none=exclude_none, + custom_encoder=custom_encoder, + sqlalchemy_safe=sqlalchemy_safe, + ) + encoded_value = jsonable_encoder( + value, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_none=exclude_none, + custom_encoder=custom_encoder, + sqlalchemy_safe=sqlalchemy_safe, + ) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)): + encoded_list = [] + for item in obj: + encoded_list.append( + jsonable_encoder( + item, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + custom_encoder=custom_encoder, + sqlalchemy_safe=sqlalchemy_safe, + ) + ) + return encoded_list + + if type(obj) in ENCODERS_BY_TYPE: + return ENCODERS_BY_TYPE[type(obj)](obj) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(obj, classes_tuple): + return encoder(obj) + + try: + data = dict(obj) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(obj) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder( + data, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + custom_encoder=custom_encoder, + sqlalchemy_safe=sqlalchemy_safe, + ) diff --git a/.venv/Lib/site-packages/fastapi/exception_handlers.py b/.venv/Lib/site-packages/fastapi/exception_handlers.py new file mode 100644 index 0000000..6c2ba7f --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/exception_handlers.py @@ -0,0 +1,34 @@ +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError +from fastapi.utils import is_body_allowed_for_status_code +from fastapi.websockets import WebSocket +from starlette.exceptions import HTTPException +from starlette.requests import Request +from starlette.responses import JSONResponse, Response +from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY, WS_1008_POLICY_VIOLATION + + +async def http_exception_handler(request: Request, exc: HTTPException) -> Response: + headers = getattr(exc, "headers", None) + if not is_body_allowed_for_status_code(exc.status_code): + return Response(status_code=exc.status_code, headers=headers) + return JSONResponse( + {"detail": exc.detail}, status_code=exc.status_code, headers=headers + ) + + +async def request_validation_exception_handler( + request: Request, exc: RequestValidationError +) -> JSONResponse: + return JSONResponse( + status_code=HTTP_422_UNPROCESSABLE_ENTITY, + content={"detail": jsonable_encoder(exc.errors())}, + ) + + +async def websocket_request_validation_exception_handler( + websocket: WebSocket, exc: WebSocketRequestValidationError +) -> None: + await websocket.close( + code=WS_1008_POLICY_VIOLATION, reason=jsonable_encoder(exc.errors()) + ) diff --git a/.venv/Lib/site-packages/fastapi/exceptions.py b/.venv/Lib/site-packages/fastapi/exceptions.py new file mode 100644 index 0000000..44d4ada --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/exceptions.py @@ -0,0 +1,176 @@ +from typing import Any, Dict, Optional, Sequence, Type, Union + +from pydantic import BaseModel, create_model +from starlette.exceptions import HTTPException as StarletteHTTPException +from starlette.exceptions import WebSocketException as StarletteWebSocketException +from typing_extensions import Annotated, Doc + + +class HTTPException(StarletteHTTPException): + """ + An HTTP exception you can raise in your own code to show errors to the client. + + This is for client errors, invalid authentication, invalid data, etc. Not for server + errors in your code. + + Read more about it in the + [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/). + + ## Example + + ```python + from fastapi import FastAPI, HTTPException + + app = FastAPI() + + items = {"foo": "The Foo Wrestlers"} + + + @app.get("/items/{item_id}") + async def read_item(item_id: str): + if item_id not in items: + raise HTTPException(status_code=404, detail="Item not found") + return {"item": items[item_id]} + ``` + """ + + def __init__( + self, + status_code: Annotated[ + int, + Doc( + """ + HTTP status code to send to the client. + """ + ), + ], + detail: Annotated[ + Any, + Doc( + """ + Any data to be sent to the client in the `detail` key of the JSON + response. + """ + ), + ] = None, + headers: Annotated[ + Optional[Dict[str, str]], + Doc( + """ + Any headers to send to the client in the response. + """ + ), + ] = None, + ) -> None: + super().__init__(status_code=status_code, detail=detail, headers=headers) + + +class WebSocketException(StarletteWebSocketException): + """ + A WebSocket exception you can raise in your own code to show errors to the client. + + This is for client errors, invalid authentication, invalid data, etc. Not for server + errors in your code. + + Read more about it in the + [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). + + ## Example + + ```python + from typing import Annotated + + from fastapi import ( + Cookie, + FastAPI, + WebSocket, + WebSocketException, + status, + ) + + app = FastAPI() + + @app.websocket("/items/{item_id}/ws") + async def websocket_endpoint( + *, + websocket: WebSocket, + session: Annotated[str | None, Cookie()] = None, + item_id: str, + ): + if session is None: + raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text(f"Session cookie is: {session}") + await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") + ``` + """ + + def __init__( + self, + code: Annotated[ + int, + Doc( + """ + A closing code from the + [valid codes defined in the specification](https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1). + """ + ), + ], + reason: Annotated[ + Union[str, None], + Doc( + """ + The reason to close the WebSocket connection. + + It is UTF-8-encoded data. The interpretation of the reason is up to the + application, it is not specified by the WebSocket specification. + + It could contain text that could be human-readable or interpretable + by the client code, etc. + """ + ), + ] = None, + ) -> None: + super().__init__(code=code, reason=reason) + + +RequestErrorModel: Type[BaseModel] = create_model("Request") +WebSocketErrorModel: Type[BaseModel] = create_model("WebSocket") + + +class FastAPIError(RuntimeError): + """ + A generic, FastAPI-specific error. + """ + + +class ValidationException(Exception): + def __init__(self, errors: Sequence[Any]) -> None: + self._errors = errors + + def errors(self) -> Sequence[Any]: + return self._errors + + +class RequestValidationError(ValidationException): + def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None: + super().__init__(errors) + self.body = body + + +class WebSocketRequestValidationError(ValidationException): + pass + + +class ResponseValidationError(ValidationException): + def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None: + super().__init__(errors) + self.body = body + + def __str__(self) -> str: + message = f"{len(self._errors)} validation errors:\n" + for err in self._errors: + message += f" {err}\n" + return message diff --git a/.venv/Lib/site-packages/fastapi/logger.py b/.venv/Lib/site-packages/fastapi/logger.py new file mode 100644 index 0000000..5b2c4ad --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/logger.py @@ -0,0 +1,3 @@ +import logging + +logger = logging.getLogger("fastapi") diff --git a/.venv/Lib/site-packages/fastapi/middleware/__init__.py b/.venv/Lib/site-packages/fastapi/middleware/__init__.py new file mode 100644 index 0000000..620296d --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/middleware/__init__.py @@ -0,0 +1 @@ +from starlette.middleware import Middleware as Middleware diff --git a/.venv/Lib/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45ad9bfb631765919e3ce2a6a250292f374ad750 GIT binary patch literal 273 zcmX@j%ge<81Z$G5GOU2~V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zYX zzL_a0IjQA|MX7$8Ot(aeOA?E6QcFrw^>X17Ma)38D;Yk6wEQY_wu%WYPAw{q2~Nz7 zaY-%CF3B&53C>JORR}1`&q_@$DUNYVEG}^j@Kh)(R&dKnEY6PcNKDR7EmCmF&&rR{ zD@)BQi}A@!iYd-4N!2X?N+qVJ7RRIkH6<2g>LcvckB`sH%PfhH*DI*J#bJ}1pHiBW bYFESobOs|37lRldm>C%v?=e^wu>m;%n=4Mc literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94bcf1b631d0e4ba64eb8bdba124d62eb851f465 GIT binary patch literal 278 zcmYLDJxjzu6x>Zf4&lVsE^P{P{UL(f5fs!r3^wwbHT%@PY~1kP8o6KMZxH+^{(}@Y zR(8U5R_=ly1CKYuFwDG;$J-*{rJmj`!hetA7s(IV><02Aw9wLuR$fUd)=8dzL_3$m z&TMf}of)kyJ}ScVJQ?=bE3)W47VV!=)Q*_a5PBAynEj&J6fPNwp;9f(ksI$isEo#J zNp6L;XDAi>qvc7~b5>f#4IHbwLCWTC<)G;CzK2tD4QxE_cfnG($Z!)vsxy=S@<}W& dy7e$RXq|qrcu1o#L5AZnh literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/middleware/__pycache__/gzip.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/middleware/__pycache__/gzip.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4553a5f6b70285d4c29e2e29153c38a227e4b428 GIT binary patch literal 278 zcmX@j%ge<81Z$G5GW>z`V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zY% z?opWqzL_a0IjQA|MX7$8Ot&P9OA?E6QcFrw^>X17dg)b}1x3t2O)D8bgY^C?a<+;I zElw>ejtNf8jd4jW&MwI>hzZV2NmU3a%FjwoE-8+2ODryN4DeJaD^_sJNi5Ee@kmV0 zPAyV!$MD~s{TOo}PaEJ@WZ07@mMrxwSg0W~ESWa=X<*9TdyS5SG2!zMRB er8Fniu80HZ1V$h(1~EP`Gcq#XV{j{C19AX#c2Ld$ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..407a12737d55955bd4921386f042d8c54b52583e GIT binary patch literal 307 zcmY*V!Ab)$5KXcmMJRalx_H}z+j|i~ETw|V;?|4g5;vnwH*3O7Tq%Bt-yrxW{=r^7 zc@qjAJeh^6I55n6Jl^9mufyRsi}O;A7ZmSXPku&hzC`H!W+>AEe#4MR~Wq*mkLGYjW5BBQG zn^5rJ$t-?880HP{&G4A#;c%BFJeA{>OZ}xEe!#!Ub}x|!rkMuI94rA?%k%8DmviX$ zFT;i+>Y1l_ZnU;|uL54=yb}~vuoxp2?pG+{mwlD&AxkG7*|xv*(VmRu6=5K`P)<&e zs_1LEFdD@?_%)W1sz? literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/middleware/__pycache__/wsgi.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/middleware/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98232764d9ff5bec5e8637fb327b784b3308394f GIT binary patch literal 278 zcmX@j%ge<81Z$G5GW>z`V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zY% z;lb{nzL_a0IjQA|MX7$8Ot&P9OA?E6QcFrw^>X17dgaCGnMKS%O)D8bgY^C?a<+;I zElw>ejtNf8jd4jW&MwI>hzZV2NmU3a%FjwoE-8+2ODryN4DeJaD^_sJNi5Ee@kmV0 zPAyV!$MD~s{TOo}PaEJ@WZ07@mMrxwSg0W~ESWa=X<*9TdyS5SG2!zMRB er8Fniu80HZ1V$h(1~EP`Gcq#XV{j{C19AXCHc*ZL literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/middleware/cors.py b/.venv/Lib/site-packages/fastapi/middleware/cors.py new file mode 100644 index 0000000..8dfaad0 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/middleware/cors.py @@ -0,0 +1 @@ +from starlette.middleware.cors import CORSMiddleware as CORSMiddleware # noqa diff --git a/.venv/Lib/site-packages/fastapi/middleware/gzip.py b/.venv/Lib/site-packages/fastapi/middleware/gzip.py new file mode 100644 index 0000000..bbeb2cc --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/middleware/gzip.py @@ -0,0 +1 @@ +from starlette.middleware.gzip import GZipMiddleware as GZipMiddleware # noqa diff --git a/.venv/Lib/site-packages/fastapi/middleware/httpsredirect.py b/.venv/Lib/site-packages/fastapi/middleware/httpsredirect.py new file mode 100644 index 0000000..b7a3d8e --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/middleware/httpsredirect.py @@ -0,0 +1,3 @@ +from starlette.middleware.httpsredirect import ( # noqa + HTTPSRedirectMiddleware as HTTPSRedirectMiddleware, +) diff --git a/.venv/Lib/site-packages/fastapi/middleware/trustedhost.py b/.venv/Lib/site-packages/fastapi/middleware/trustedhost.py new file mode 100644 index 0000000..08d7e03 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/middleware/trustedhost.py @@ -0,0 +1,3 @@ +from starlette.middleware.trustedhost import ( # noqa + TrustedHostMiddleware as TrustedHostMiddleware, +) diff --git a/.venv/Lib/site-packages/fastapi/middleware/wsgi.py b/.venv/Lib/site-packages/fastapi/middleware/wsgi.py new file mode 100644 index 0000000..c4c6a79 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/middleware/wsgi.py @@ -0,0 +1 @@ +from starlette.middleware.wsgi import WSGIMiddleware as WSGIMiddleware # noqa diff --git a/.venv/Lib/site-packages/fastapi/openapi/__init__.py b/.venv/Lib/site-packages/fastapi/openapi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70fa8e4bec01864e39c555bdc5539524f9ad7978 GIT binary patch literal 212 zcmX@j%ge<81Z$G5GC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!Dt5Mt2`x@7Dvk+G z%#CqLEzT~paZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%IiqyaT07G&z@7o_Ha==k`|yv&mLc)fzkTO2mI d`6;D2sdh!IKsPV~aWRPTk(rT^v4|PS0sxPQI_Ura literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3104fe06bc6cedffb2ec5c4f10cffcb85a5383a0 GIT binary patch literal 382 zcmZus!AiqG5Y0BVNNJUVy?F@erH3~6B8Av&O`)}m*`k((vL>rd+hjvF7FrSXE5x%W z!9VfhvFz0!5PI|E2K45GnR)Xb18?5T<)WtW<~1kJYJO(RA9BBBv!}?DHqm_T1&lNw zjDVjT<(7Ia-zq3e>L(CO9SyO}JJ7Oa9t~V9w~&R~jx5-)2Qge#4@{4TF^^c33R8F! z7E)oZ_WzadLhiHq{b@~?l|IDIz;?&yUF?jH2lk~bp25x-sn)%arHo_PM?DL}S8bDJ z{!ZIXG88Nk#HAsznV6+KCT`#}10{UQyi|}56{&@~#zGjKITbVFP;bT(!{$>?8VeRJ zNH4e|B1l<1Ru;Wsf?TU38V4qiS(HtWM@mo+HR7e*K2oV~=d6|Ns1QJvsH*m*0|4Y6 U{Lm`v^5H{qy<2}QB_-wj0N87D_W%F@ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..012af4fd5049dff6fb003bf769e43e624a69c7f4 GIT binary patch literal 10832 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 R*lgdjRZP@4zEy1p{698rohASP literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8fd0895f7b636fe3d1ff1eeba5c8754c6e0d688 GIT binary patch literal 21069 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 BkQx90 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/openapi/constants.py b/.venv/Lib/site-packages/fastapi/openapi/constants.py new file mode 100644 index 0000000..d724ee3 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/openapi/constants.py @@ -0,0 +1,3 @@ +METHODS_WITH_BODY = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"} +REF_PREFIX = "#/components/schemas/" +REF_TEMPLATE = "#/components/schemas/{model}" diff --git a/.venv/Lib/site-packages/fastapi/openapi/docs.py b/.venv/Lib/site-packages/fastapi/openapi/docs.py new file mode 100644 index 0000000..c2ec358 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/openapi/docs.py @@ -0,0 +1,344 @@ +import json +from typing import Any, Dict, Optional + +from fastapi.encoders import jsonable_encoder +from starlette.responses import HTMLResponse +from typing_extensions import Annotated, Doc + +swagger_ui_default_parameters: Annotated[ + Dict[str, Any], + Doc( + """ + Default configurations for Swagger UI. + + You can use it as a template to add any other configurations needed. + """ + ), +] = { + "dom_id": "#swagger-ui", + "layout": "BaseLayout", + "deepLinking": True, + "showExtensions": True, + "showCommonExtensions": True, +} + + +def get_swagger_ui_html( + *, + openapi_url: Annotated[ + str, + Doc( + """ + The OpenAPI URL that Swagger UI should load and use. + + This is normally done automatically by FastAPI using the default URL + `/openapi.json`. + """ + ), + ], + title: Annotated[ + str, + Doc( + """ + The HTML `` content, normally shown in the browser tab. + """ + ), + ], + swagger_js_url: Annotated[ + str, + Doc( + """ + The URL to use to load the Swagger UI JavaScript. + + It is normally set to a CDN URL. + """ + ), + ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js", + swagger_css_url: Annotated[ + str, + Doc( + """ + The URL to use to load the Swagger UI CSS. + + It is normally set to a CDN URL. + """ + ), + ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css", + swagger_favicon_url: Annotated[ + str, + Doc( + """ + The URL of the favicon to use. It is normally shown in the browser tab. + """ + ), + ] = "https://fastapi.tiangolo.com/img/favicon.png", + oauth2_redirect_url: Annotated[ + Optional[str], + Doc( + """ + The OAuth2 redirect URL, it is normally automatically handled by FastAPI. + """ + ), + ] = None, + init_oauth: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + A dictionary with Swagger UI OAuth2 initialization configurations. + """ + ), + ] = None, + swagger_ui_parameters: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Configuration parameters for Swagger UI. + + It defaults to [swagger_ui_default_parameters][fastapi.openapi.docs.swagger_ui_default_parameters]. + """ + ), + ] = None, +) -> HTMLResponse: + """ + Generate and return the HTML that loads Swagger UI for the interactive + API docs (normally served at `/docs`). + + You would only call this function yourself if you needed to override some parts, + for example the URLs to use to load Swagger UI's JavaScript and CSS. + + Read more about it in the + [FastAPI docs for Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/) + and the [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/). + """ + current_swagger_ui_parameters = swagger_ui_default_parameters.copy() + if swagger_ui_parameters: + current_swagger_ui_parameters.update(swagger_ui_parameters) + + html = f""" + <!DOCTYPE html> + <html> + <head> + <link type="text/css" rel="stylesheet" href="{swagger_css_url}"> + <link rel="shortcut icon" href="{swagger_favicon_url}"> + <title>{title} + + +
+
+ + + + + + """ + return HTMLResponse(html) + + +def get_redoc_html( + *, + openapi_url: Annotated[ + str, + Doc( + """ + The OpenAPI URL that ReDoc should load and use. + + This is normally done automatically by FastAPI using the default URL + `/openapi.json`. + """ + ), + ], + title: Annotated[ + str, + Doc( + """ + The HTML `` content, normally shown in the browser tab. + """ + ), + ], + redoc_js_url: Annotated[ + str, + Doc( + """ + The URL to use to load the ReDoc JavaScript. + + It is normally set to a CDN URL. + """ + ), + ] = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js", + redoc_favicon_url: Annotated[ + str, + Doc( + """ + The URL of the favicon to use. It is normally shown in the browser tab. + """ + ), + ] = "https://fastapi.tiangolo.com/img/favicon.png", + with_google_fonts: Annotated[ + bool, + Doc( + """ + Load and use Google Fonts. + """ + ), + ] = True, +) -> HTMLResponse: + """ + Generate and return the HTML response that loads ReDoc for the alternative + API docs (normally served at `/redoc`). + + You would only call this function yourself if you needed to override some parts, + for example the URLs to use to load ReDoc's JavaScript and CSS. + + Read more about it in the + [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/). + """ + html = f""" + <!DOCTYPE html> + <html> + <head> + <title>{title} + + + + """ + if with_google_fonts: + html += """ + + """ + html += f""" + + + + + + + + + + + """ + return HTMLResponse(html) + + +def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse: + """ + Generate the HTML response with the OAuth2 redirection for Swagger UI. + + You normally don't need to use or change this. + """ + # copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html + html = """ + + + + Swagger UI: OAuth2 Redirect + + + + + + """ + return HTMLResponse(content=html) diff --git a/.venv/Lib/site-packages/fastapi/openapi/models.py b/.venv/Lib/site-packages/fastapi/openapi/models.py new file mode 100644 index 0000000..ed07b40 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/openapi/models.py @@ -0,0 +1,445 @@ +from enum import Enum +from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Type, Union + +from fastapi._compat import ( + PYDANTIC_V2, + CoreSchema, + GetJsonSchemaHandler, + JsonSchemaValue, + _model_rebuild, + with_info_plain_validator_function, +) +from fastapi.logger import logger +from pydantic import AnyUrl, BaseModel, Field +from typing_extensions import Annotated, Literal, TypedDict +from typing_extensions import deprecated as typing_deprecated + +try: + import email_validator + + assert email_validator # make autoflake ignore the unused import + from pydantic import EmailStr +except ImportError: # pragma: no cover + + class EmailStr(str): # type: ignore + @classmethod + def __get_validators__(cls) -> Iterable[Callable[..., Any]]: + yield cls.validate + + @classmethod + def validate(cls, v: Any) -> str: + logger.warning( + "email-validator not installed, email fields will be treated as str.\n" + "To install, run: pip install email-validator" + ) + return str(v) + + @classmethod + def _validate(cls, __input_value: Any, _: Any) -> str: + logger.warning( + "email-validator not installed, email fields will be treated as str.\n" + "To install, run: pip install email-validator" + ) + return str(__input_value) + + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + return {"type": "string", "format": "email"} + + @classmethod + def __get_pydantic_core_schema__( + cls, source: Type[Any], handler: Callable[[Any], CoreSchema] + ) -> CoreSchema: + return with_info_plain_validator_function(cls._validate) + + +class BaseModelWithConfig(BaseModel): + if PYDANTIC_V2: + model_config = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +class Contact(BaseModelWithConfig): + name: Optional[str] = None + url: Optional[AnyUrl] = None + email: Optional[EmailStr] = None + + +class License(BaseModelWithConfig): + name: str + identifier: Optional[str] = None + url: Optional[AnyUrl] = None + + +class Info(BaseModelWithConfig): + title: str + summary: Optional[str] = None + description: Optional[str] = None + termsOfService: Optional[str] = None + contact: Optional[Contact] = None + license: Optional[License] = None + version: str + + +class ServerVariable(BaseModelWithConfig): + enum: Annotated[Optional[List[str]], Field(min_length=1)] = None + default: str + description: Optional[str] = None + + +class Server(BaseModelWithConfig): + url: Union[AnyUrl, str] + description: Optional[str] = None + variables: Optional[Dict[str, ServerVariable]] = None + + +class Reference(BaseModel): + ref: str = Field(alias="$ref") + + +class Discriminator(BaseModel): + propertyName: str + mapping: Optional[Dict[str, str]] = None + + +class XML(BaseModelWithConfig): + name: Optional[str] = None + namespace: Optional[str] = None + prefix: Optional[str] = None + attribute: Optional[bool] = None + wrapped: Optional[bool] = None + + +class ExternalDocumentation(BaseModelWithConfig): + description: Optional[str] = None + url: AnyUrl + + +class Schema(BaseModelWithConfig): + # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu + # Core Vocabulary + schema_: Optional[str] = Field(default=None, alias="$schema") + vocabulary: Optional[str] = Field(default=None, alias="$vocabulary") + id: Optional[str] = Field(default=None, alias="$id") + anchor: Optional[str] = Field(default=None, alias="$anchor") + dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor") + ref: Optional[str] = Field(default=None, alias="$ref") + dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef") + defs: Optional[Dict[str, "SchemaOrBool"]] = Field(default=None, alias="$defs") + comment: Optional[str] = Field(default=None, alias="$comment") + # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s + # A Vocabulary for Applying Subschemas + allOf: Optional[List["SchemaOrBool"]] = None + anyOf: Optional[List["SchemaOrBool"]] = None + oneOf: Optional[List["SchemaOrBool"]] = None + not_: Optional["SchemaOrBool"] = Field(default=None, alias="not") + if_: Optional["SchemaOrBool"] = Field(default=None, alias="if") + then: Optional["SchemaOrBool"] = None + else_: Optional["SchemaOrBool"] = Field(default=None, alias="else") + dependentSchemas: Optional[Dict[str, "SchemaOrBool"]] = None + prefixItems: Optional[List["SchemaOrBool"]] = None + # TODO: uncomment and remove below when deprecating Pydantic v1 + # It generales a list of schemas for tuples, before prefixItems was available + # items: Optional["SchemaOrBool"] = None + items: Optional[Union["SchemaOrBool", List["SchemaOrBool"]]] = None + contains: Optional["SchemaOrBool"] = None + properties: Optional[Dict[str, "SchemaOrBool"]] = None + patternProperties: Optional[Dict[str, "SchemaOrBool"]] = None + additionalProperties: Optional["SchemaOrBool"] = None + propertyNames: Optional["SchemaOrBool"] = None + unevaluatedItems: Optional["SchemaOrBool"] = None + unevaluatedProperties: Optional["SchemaOrBool"] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural + # A Vocabulary for Structural Validation + type: Optional[str] = None + enum: Optional[List[Any]] = None + const: Optional[Any] = None + multipleOf: Optional[float] = Field(default=None, gt=0) + maximum: Optional[float] = None + exclusiveMaximum: Optional[float] = None + minimum: Optional[float] = None + exclusiveMinimum: Optional[float] = None + maxLength: Optional[int] = Field(default=None, ge=0) + minLength: Optional[int] = Field(default=None, ge=0) + pattern: Optional[str] = None + maxItems: Optional[int] = Field(default=None, ge=0) + minItems: Optional[int] = Field(default=None, ge=0) + uniqueItems: Optional[bool] = None + maxContains: Optional[int] = Field(default=None, ge=0) + minContains: Optional[int] = Field(default=None, ge=0) + maxProperties: Optional[int] = Field(default=None, ge=0) + minProperties: Optional[int] = Field(default=None, ge=0) + required: Optional[List[str]] = None + dependentRequired: Optional[Dict[str, Set[str]]] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c + # Vocabularies for Semantic Content With "format" + format: Optional[str] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten + # A Vocabulary for the Contents of String-Encoded Data + contentEncoding: Optional[str] = None + contentMediaType: Optional[str] = None + contentSchema: Optional["SchemaOrBool"] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta + # A Vocabulary for Basic Meta-Data Annotations + title: Optional[str] = None + description: Optional[str] = None + default: Optional[Any] = None + deprecated: Optional[bool] = None + readOnly: Optional[bool] = None + writeOnly: Optional[bool] = None + examples: Optional[List[Any]] = None + # Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object + # Schema Object + discriminator: Optional[Discriminator] = None + xml: Optional[XML] = None + externalDocs: Optional[ExternalDocumentation] = None + example: Annotated[ + Optional[Any], + typing_deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = None + + +# Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents +# A JSON Schema MUST be an object or a boolean. +SchemaOrBool = Union[Schema, bool] + + +class Example(TypedDict, total=False): + summary: Optional[str] + description: Optional[str] + value: Optional[Any] + externalValue: Optional[AnyUrl] + + if PYDANTIC_V2: # type: ignore [misc] + __pydantic_config__ = {"extra": "allow"} + + else: + + class Config: + extra = "allow" + + +class ParameterInType(Enum): + query = "query" + header = "header" + path = "path" + cookie = "cookie" + + +class Encoding(BaseModelWithConfig): + contentType: Optional[str] = None + headers: Optional[Dict[str, Union["Header", Reference]]] = None + style: Optional[str] = None + explode: Optional[bool] = None + allowReserved: Optional[bool] = None + + +class MediaType(BaseModelWithConfig): + schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") + example: Optional[Any] = None + examples: Optional[Dict[str, Union[Example, Reference]]] = None + encoding: Optional[Dict[str, Encoding]] = None + + +class ParameterBase(BaseModelWithConfig): + description: Optional[str] = None + required: Optional[bool] = None + deprecated: Optional[bool] = None + # Serialization rules for simple scenarios + style: Optional[str] = None + explode: Optional[bool] = None + allowReserved: Optional[bool] = None + schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") + example: Optional[Any] = None + examples: Optional[Dict[str, Union[Example, Reference]]] = None + # Serialization rules for more complex scenarios + content: Optional[Dict[str, MediaType]] = None + + +class Parameter(ParameterBase): + name: str + in_: ParameterInType = Field(alias="in") + + +class Header(ParameterBase): + pass + + +class RequestBody(BaseModelWithConfig): + description: Optional[str] = None + content: Dict[str, MediaType] + required: Optional[bool] = None + + +class Link(BaseModelWithConfig): + operationRef: Optional[str] = None + operationId: Optional[str] = None + parameters: Optional[Dict[str, Union[Any, str]]] = None + requestBody: Optional[Union[Any, str]] = None + description: Optional[str] = None + server: Optional[Server] = None + + +class Response(BaseModelWithConfig): + description: str + headers: Optional[Dict[str, Union[Header, Reference]]] = None + content: Optional[Dict[str, MediaType]] = None + links: Optional[Dict[str, Union[Link, Reference]]] = None + + +class Operation(BaseModelWithConfig): + tags: Optional[List[str]] = None + summary: Optional[str] = None + description: Optional[str] = None + externalDocs: Optional[ExternalDocumentation] = None + operationId: Optional[str] = None + parameters: Optional[List[Union[Parameter, Reference]]] = None + requestBody: Optional[Union[RequestBody, Reference]] = None + # Using Any for Specification Extensions + responses: Optional[Dict[str, Union[Response, Any]]] = None + callbacks: Optional[Dict[str, Union[Dict[str, "PathItem"], Reference]]] = None + deprecated: Optional[bool] = None + security: Optional[List[Dict[str, List[str]]]] = None + servers: Optional[List[Server]] = None + + +class PathItem(BaseModelWithConfig): + ref: Optional[str] = Field(default=None, alias="$ref") + summary: Optional[str] = None + description: Optional[str] = None + get: Optional[Operation] = None + put: Optional[Operation] = None + post: Optional[Operation] = None + delete: Optional[Operation] = None + options: Optional[Operation] = None + head: Optional[Operation] = None + patch: Optional[Operation] = None + trace: Optional[Operation] = None + servers: Optional[List[Server]] = None + parameters: Optional[List[Union[Parameter, Reference]]] = None + + +class SecuritySchemeType(Enum): + apiKey = "apiKey" + http = "http" + oauth2 = "oauth2" + openIdConnect = "openIdConnect" + + +class SecurityBase(BaseModelWithConfig): + type_: SecuritySchemeType = Field(alias="type") + description: Optional[str] = None + + +class APIKeyIn(Enum): + query = "query" + header = "header" + cookie = "cookie" + + +class APIKey(SecurityBase): + type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias="type") + in_: APIKeyIn = Field(alias="in") + name: str + + +class HTTPBase(SecurityBase): + type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias="type") + scheme: str + + +class HTTPBearer(HTTPBase): + scheme: Literal["bearer"] = "bearer" + bearerFormat: Optional[str] = None + + +class OAuthFlow(BaseModelWithConfig): + refreshUrl: Optional[str] = None + scopes: Dict[str, str] = {} + + +class OAuthFlowImplicit(OAuthFlow): + authorizationUrl: str + + +class OAuthFlowPassword(OAuthFlow): + tokenUrl: str + + +class OAuthFlowClientCredentials(OAuthFlow): + tokenUrl: str + + +class OAuthFlowAuthorizationCode(OAuthFlow): + authorizationUrl: str + tokenUrl: str + + +class OAuthFlows(BaseModelWithConfig): + implicit: Optional[OAuthFlowImplicit] = None + password: Optional[OAuthFlowPassword] = None + clientCredentials: Optional[OAuthFlowClientCredentials] = None + authorizationCode: Optional[OAuthFlowAuthorizationCode] = None + + +class OAuth2(SecurityBase): + type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias="type") + flows: OAuthFlows + + +class OpenIdConnect(SecurityBase): + type_: SecuritySchemeType = Field( + default=SecuritySchemeType.openIdConnect, alias="type" + ) + openIdConnectUrl: str + + +SecurityScheme = Union[APIKey, HTTPBase, OAuth2, OpenIdConnect, HTTPBearer] + + +class Components(BaseModelWithConfig): + schemas: Optional[Dict[str, Union[Schema, Reference]]] = None + responses: Optional[Dict[str, Union[Response, Reference]]] = None + parameters: Optional[Dict[str, Union[Parameter, Reference]]] = None + examples: Optional[Dict[str, Union[Example, Reference]]] = None + requestBodies: Optional[Dict[str, Union[RequestBody, Reference]]] = None + headers: Optional[Dict[str, Union[Header, Reference]]] = None + securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None + links: Optional[Dict[str, Union[Link, Reference]]] = None + # Using Any for Specification Extensions + callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference, Any]]] = None + pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None + + +class Tag(BaseModelWithConfig): + name: str + description: Optional[str] = None + externalDocs: Optional[ExternalDocumentation] = None + + +class OpenAPI(BaseModelWithConfig): + openapi: str + info: Info + jsonSchemaDialect: Optional[str] = None + servers: Optional[List[Server]] = None + # Using Any for Specification Extensions + paths: Optional[Dict[str, Union[PathItem, Any]]] = None + webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None + components: Optional[Components] = None + security: Optional[List[Dict[str, List[str]]]] = None + tags: Optional[List[Tag]] = None + externalDocs: Optional[ExternalDocumentation] = None + + +_model_rebuild(Schema) +_model_rebuild(Operation) +_model_rebuild(Encoding) diff --git a/.venv/Lib/site-packages/fastapi/openapi/utils.py b/.venv/Lib/site-packages/fastapi/openapi/utils.py new file mode 100644 index 0000000..808646c --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/openapi/utils.py @@ -0,0 +1,569 @@ +import http.client +import inspect +import warnings +from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast + +from fastapi import routing +from fastapi._compat import ( + GenerateJsonSchema, + JsonSchemaValue, + ModelField, + Undefined, + get_compat_model_name_map, + get_definitions, + get_schema_from_model_field, + lenient_issubclass, +) +from fastapi.datastructures import DefaultPlaceholder +from fastapi.dependencies.models import Dependant +from fastapi.dependencies.utils import ( + _get_flat_fields_from_params, + get_flat_dependant, + get_flat_params, +) +from fastapi.encoders import jsonable_encoder +from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX, REF_TEMPLATE +from fastapi.openapi.models import OpenAPI +from fastapi.params import Body, ParamTypes +from fastapi.responses import Response +from fastapi.types import ModelNameMap +from fastapi.utils import ( + deep_dict_update, + generate_operation_id_for_path, + is_body_allowed_for_status_code, +) +from pydantic import BaseModel +from starlette.responses import JSONResponse +from starlette.routing import BaseRoute +from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY +from typing_extensions import Literal + +validation_error_definition = { + "title": "ValidationError", + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + "required": ["loc", "msg", "type"], +} + +validation_error_response_definition = { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": REF_PREFIX + "ValidationError"}, + } + }, +} + +status_code_ranges: Dict[str, str] = { + "1XX": "Information", + "2XX": "Success", + "3XX": "Redirection", + "4XX": "Client Error", + "5XX": "Server Error", + "DEFAULT": "Default Response", +} + + +def get_openapi_security_definitions( + flat_dependant: Dependant, +) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: + security_definitions = {} + operation_security = [] + for security_requirement in flat_dependant.security_requirements: + security_definition = jsonable_encoder( + security_requirement.security_scheme.model, + by_alias=True, + exclude_none=True, + ) + security_name = security_requirement.security_scheme.scheme_name + security_definitions[security_name] = security_definition + operation_security.append({security_name: security_requirement.scopes}) + return security_definitions, operation_security + + +def _get_openapi_operation_parameters( + *, + dependant: Dependant, + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + separate_input_output_schemas: bool = True, +) -> List[Dict[str, Any]]: + parameters = [] + flat_dependant = get_flat_dependant(dependant, skip_repeats=True) + path_params = _get_flat_fields_from_params(flat_dependant.path_params) + query_params = _get_flat_fields_from_params(flat_dependant.query_params) + header_params = _get_flat_fields_from_params(flat_dependant.header_params) + cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params) + parameter_groups = [ + (ParamTypes.path, path_params), + (ParamTypes.query, query_params), + (ParamTypes.header, header_params), + (ParamTypes.cookie, cookie_params), + ] + default_convert_underscores = True + if len(flat_dependant.header_params) == 1: + first_field = flat_dependant.header_params[0] + if lenient_issubclass(first_field.type_, BaseModel): + default_convert_underscores = getattr( + first_field.field_info, "convert_underscores", True + ) + for param_type, param_group in parameter_groups: + for param in param_group: + field_info = param.field_info + # field_info = cast(Param, field_info) + if not getattr(field_info, "include_in_schema", True): + continue + param_schema = get_schema_from_model_field( + field=param, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + name = param.alias + convert_underscores = getattr( + param.field_info, + "convert_underscores", + default_convert_underscores, + ) + if ( + param_type == ParamTypes.header + and param.alias == param.name + and convert_underscores + ): + name = param.name.replace("_", "-") + + parameter = { + "name": name, + "in": param_type.value, + "required": param.required, + "schema": param_schema, + } + if field_info.description: + parameter["description"] = field_info.description + openapi_examples = getattr(field_info, "openapi_examples", None) + example = getattr(field_info, "example", None) + if openapi_examples: + parameter["examples"] = jsonable_encoder(openapi_examples) + elif example != Undefined: + parameter["example"] = jsonable_encoder(example) + if getattr(field_info, "deprecated", None): + parameter["deprecated"] = True + parameters.append(parameter) + return parameters + + +def get_openapi_operation_request_body( + *, + body_field: Optional[ModelField], + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + separate_input_output_schemas: bool = True, +) -> Optional[Dict[str, Any]]: + if not body_field: + return None + assert isinstance(body_field, ModelField) + body_schema = get_schema_from_model_field( + field=body_field, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + field_info = cast(Body, body_field.field_info) + request_media_type = field_info.media_type + required = body_field.required + request_body_oai: Dict[str, Any] = {} + if required: + request_body_oai["required"] = required + request_media_content: Dict[str, Any] = {"schema": body_schema} + if field_info.openapi_examples: + request_media_content["examples"] = jsonable_encoder( + field_info.openapi_examples + ) + elif field_info.example != Undefined: + request_media_content["example"] = jsonable_encoder(field_info.example) + request_body_oai["content"] = {request_media_type: request_media_content} + return request_body_oai + + +def generate_operation_id( + *, route: routing.APIRoute, method: str +) -> str: # pragma: nocover + warnings.warn( + "fastapi.openapi.utils.generate_operation_id() was deprecated, " + "it is not used internally, and will be removed soon", + DeprecationWarning, + stacklevel=2, + ) + if route.operation_id: + return route.operation_id + path: str = route.path_format + return generate_operation_id_for_path(name=route.name, path=path, method=method) + + +def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str: + if route.summary: + return route.summary + return route.name.replace("_", " ").title() + + +def get_openapi_operation_metadata( + *, route: routing.APIRoute, method: str, operation_ids: Set[str] +) -> Dict[str, Any]: + operation: Dict[str, Any] = {} + if route.tags: + operation["tags"] = route.tags + operation["summary"] = generate_operation_summary(route=route, method=method) + if route.description: + operation["description"] = route.description + operation_id = route.operation_id or route.unique_id + if operation_id in operation_ids: + message = ( + f"Duplicate Operation ID {operation_id} for function " + + f"{route.endpoint.__name__}" + ) + file_name = getattr(route.endpoint, "__globals__", {}).get("__file__") + if file_name: + message += f" at {file_name}" + warnings.warn(message, stacklevel=1) + operation_ids.add(operation_id) + operation["operationId"] = operation_id + if route.deprecated: + operation["deprecated"] = route.deprecated + return operation + + +def get_openapi_path( + *, + route: routing.APIRoute, + operation_ids: Set[str], + schema_generator: GenerateJsonSchema, + model_name_map: ModelNameMap, + field_mapping: Dict[ + Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ], + separate_input_output_schemas: bool = True, +) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]: + path = {} + security_schemes: Dict[str, Any] = {} + definitions: Dict[str, Any] = {} + assert route.methods is not None, "Methods must be a list" + if isinstance(route.response_class, DefaultPlaceholder): + current_response_class: Type[Response] = route.response_class.value + else: + current_response_class = route.response_class + assert current_response_class, "A response class is needed to generate OpenAPI" + route_response_media_type: Optional[str] = current_response_class.media_type + if route.include_in_schema: + for method in route.methods: + operation = get_openapi_operation_metadata( + route=route, method=method, operation_ids=operation_ids + ) + parameters: List[Dict[str, Any]] = [] + flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True) + security_definitions, operation_security = get_openapi_security_definitions( + flat_dependant=flat_dependant + ) + if operation_security: + operation.setdefault("security", []).extend(operation_security) + if security_definitions: + security_schemes.update(security_definitions) + operation_parameters = _get_openapi_operation_parameters( + dependant=route.dependant, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + parameters.extend(operation_parameters) + if parameters: + all_parameters = { + (param["in"], param["name"]): param for param in parameters + } + required_parameters = { + (param["in"], param["name"]): param + for param in parameters + if param.get("required") + } + # Make sure required definitions of the same parameter take precedence + # over non-required definitions + all_parameters.update(required_parameters) + operation["parameters"] = list(all_parameters.values()) + if method in METHODS_WITH_BODY: + request_body_oai = get_openapi_operation_request_body( + body_field=route.body_field, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + if request_body_oai: + operation["requestBody"] = request_body_oai + if route.callbacks: + callbacks = {} + for callback in route.callbacks: + if isinstance(callback, routing.APIRoute): + ( + cb_path, + cb_security_schemes, + cb_definitions, + ) = get_openapi_path( + route=callback, + operation_ids=operation_ids, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + callbacks[callback.name] = {callback.path: cb_path} + operation["callbacks"] = callbacks + if route.status_code is not None: + status_code = str(route.status_code) + else: + # It would probably make more sense for all response classes to have an + # explicit default status_code, and to extract it from them, instead of + # doing this inspection tricks, that would probably be in the future + # TODO: probably make status_code a default class attribute for all + # responses in Starlette + response_signature = inspect.signature(current_response_class.__init__) + status_code_param = response_signature.parameters.get("status_code") + if status_code_param is not None: + if isinstance(status_code_param.default, int): + status_code = str(status_code_param.default) + operation.setdefault("responses", {}).setdefault(status_code, {})[ + "description" + ] = route.response_description + if route_response_media_type and is_body_allowed_for_status_code( + route.status_code + ): + response_schema = {"type": "string"} + if lenient_issubclass(current_response_class, JSONResponse): + if route.response_field: + response_schema = get_schema_from_model_field( + field=route.response_field, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + else: + response_schema = {} + operation.setdefault("responses", {}).setdefault( + status_code, {} + ).setdefault("content", {}).setdefault(route_response_media_type, {})[ + "schema" + ] = response_schema + if route.responses: + operation_responses = operation.setdefault("responses", {}) + for ( + additional_status_code, + additional_response, + ) in route.responses.items(): + process_response = additional_response.copy() + process_response.pop("model", None) + status_code_key = str(additional_status_code).upper() + if status_code_key == "DEFAULT": + status_code_key = "default" + openapi_response = operation_responses.setdefault( + status_code_key, {} + ) + assert isinstance(process_response, dict), ( + "An additional response must be a dict" + ) + field = route.response_fields.get(additional_status_code) + additional_field_schema: Optional[Dict[str, Any]] = None + if field: + additional_field_schema = get_schema_from_model_field( + field=field, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + media_type = route_response_media_type or "application/json" + additional_schema = ( + process_response.setdefault("content", {}) + .setdefault(media_type, {}) + .setdefault("schema", {}) + ) + deep_dict_update(additional_schema, additional_field_schema) + status_text: Optional[str] = status_code_ranges.get( + str(additional_status_code).upper() + ) or http.client.responses.get(int(additional_status_code)) + description = ( + process_response.get("description") + or openapi_response.get("description") + or status_text + or "Additional Response" + ) + deep_dict_update(openapi_response, process_response) + openapi_response["description"] = description + http422 = str(HTTP_422_UNPROCESSABLE_ENTITY) + all_route_params = get_flat_params(route.dependant) + if (all_route_params or route.body_field) and not any( + status in operation["responses"] + for status in [http422, "4XX", "default"] + ): + operation["responses"][http422] = { + "description": "Validation Error", + "content": { + "application/json": { + "schema": {"$ref": REF_PREFIX + "HTTPValidationError"} + } + }, + } + if "ValidationError" not in definitions: + definitions.update( + { + "ValidationError": validation_error_definition, + "HTTPValidationError": validation_error_response_definition, + } + ) + if route.openapi_extra: + deep_dict_update(operation, route.openapi_extra) + path[method.lower()] = operation + return path, security_schemes, definitions + + +def get_fields_from_routes( + routes: Sequence[BaseRoute], +) -> List[ModelField]: + body_fields_from_routes: List[ModelField] = [] + responses_from_routes: List[ModelField] = [] + request_fields_from_routes: List[ModelField] = [] + callback_flat_models: List[ModelField] = [] + for route in routes: + if getattr(route, "include_in_schema", None) and isinstance( + route, routing.APIRoute + ): + if route.body_field: + assert isinstance(route.body_field, ModelField), ( + "A request body must be a Pydantic Field" + ) + body_fields_from_routes.append(route.body_field) + if route.response_field: + responses_from_routes.append(route.response_field) + if route.response_fields: + responses_from_routes.extend(route.response_fields.values()) + if route.callbacks: + callback_flat_models.extend(get_fields_from_routes(route.callbacks)) + params = get_flat_params(route.dependant) + request_fields_from_routes.extend(params) + + flat_models = callback_flat_models + list( + body_fields_from_routes + responses_from_routes + request_fields_from_routes + ) + return flat_models + + +def get_openapi( + *, + title: str, + version: str, + openapi_version: str = "3.1.0", + summary: Optional[str] = None, + description: Optional[str] = None, + routes: Sequence[BaseRoute], + webhooks: Optional[Sequence[BaseRoute]] = None, + tags: Optional[List[Dict[str, Any]]] = None, + servers: Optional[List[Dict[str, Union[str, Any]]]] = None, + terms_of_service: Optional[str] = None, + contact: Optional[Dict[str, Union[str, Any]]] = None, + license_info: Optional[Dict[str, Union[str, Any]]] = None, + separate_input_output_schemas: bool = True, +) -> Dict[str, Any]: + info: Dict[str, Any] = {"title": title, "version": version} + if summary: + info["summary"] = summary + if description: + info["description"] = description + if terms_of_service: + info["termsOfService"] = terms_of_service + if contact: + info["contact"] = contact + if license_info: + info["license"] = license_info + output: Dict[str, Any] = {"openapi": openapi_version, "info": info} + if servers: + output["servers"] = servers + components: Dict[str, Dict[str, Any]] = {} + paths: Dict[str, Dict[str, Any]] = {} + webhook_paths: Dict[str, Dict[str, Any]] = {} + operation_ids: Set[str] = set() + all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or [])) + model_name_map = get_compat_model_name_map(all_fields) + schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE) + field_mapping, definitions = get_definitions( + fields=all_fields, + schema_generator=schema_generator, + model_name_map=model_name_map, + separate_input_output_schemas=separate_input_output_schemas, + ) + for route in routes or []: + if isinstance(route, routing.APIRoute): + result = get_openapi_path( + route=route, + operation_ids=operation_ids, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + if result: + path, security_schemes, path_definitions = result + if path: + paths.setdefault(route.path_format, {}).update(path) + if security_schemes: + components.setdefault("securitySchemes", {}).update( + security_schemes + ) + if path_definitions: + definitions.update(path_definitions) + for webhook in webhooks or []: + if isinstance(webhook, routing.APIRoute): + result = get_openapi_path( + route=webhook, + operation_ids=operation_ids, + schema_generator=schema_generator, + model_name_map=model_name_map, + field_mapping=field_mapping, + separate_input_output_schemas=separate_input_output_schemas, + ) + if result: + path, security_schemes, path_definitions = result + if path: + webhook_paths.setdefault(webhook.path_format, {}).update(path) + if security_schemes: + components.setdefault("securitySchemes", {}).update( + security_schemes + ) + if path_definitions: + definitions.update(path_definitions) + if definitions: + components["schemas"] = {k: definitions[k] for k in sorted(definitions)} + if components: + output["components"] = components + output["paths"] = paths + if webhook_paths: + output["webhooks"] = webhook_paths + if tags: + output["tags"] = tags + return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) # type: ignore diff --git a/.venv/Lib/site-packages/fastapi/param_functions.py b/.venv/Lib/site-packages/fastapi/param_functions.py new file mode 100644 index 0000000..b362162 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/param_functions.py @@ -0,0 +1,2360 @@ +from typing import Any, Callable, Dict, List, Optional, Sequence, Union + +from fastapi import params +from fastapi._compat import Undefined +from fastapi.openapi.models import Example +from typing_extensions import Annotated, Doc, deprecated + +_Unset: Any = Undefined + + +def Path( # noqa: N802 + default: Annotated[ + Any, + Doc( + """ + Default value if the parameter field is not set. + + This doesn't affect `Path` parameters as the value is always required. + The parameter is available only for compatibility. + """ + ), + ] = ..., + *, + default_factory: Annotated[ + Union[Callable[[], Any], None], + Doc( + """ + A callable to generate the default value. + + This doesn't affect `Path` parameters as the value is always required. + The parameter is available only for compatibility. + """ + ), + ] = _Unset, + alias: Annotated[ + Optional[str], + Doc( + """ + An alternative name for the parameter field. + + This will be used to extract the data and for the generated OpenAPI. + It is particularly useful when you can't use the name you want because it + is a Python reserved keyword or similar. + """ + ), + ] = None, + alias_priority: Annotated[ + Union[int, None], + Doc( + """ + Priority of the alias. This affects whether an alias generator is used. + """ + ), + ] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Whitelist' validation step. The parameter field will be the single one + allowed by the alias or set of aliases defined. + """ + ), + ] = None, + serialization_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Blacklist' validation step. The vanilla parameter field will be the + single one of the alias' or set of aliases' fields and all the other + fields will be ignored at serialization time. + """ + ), + ] = None, + title: Annotated[ + Optional[str], + Doc( + """ + Human-readable title. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Human-readable description. + """ + ), + ] = None, + gt: Annotated[ + Optional[float], + Doc( + """ + Greater than. If set, value must be greater than this. Only applicable to + numbers. + """ + ), + ] = None, + ge: Annotated[ + Optional[float], + Doc( + """ + Greater than or equal. If set, value must be greater than or equal to + this. Only applicable to numbers. + """ + ), + ] = None, + lt: Annotated[ + Optional[float], + Doc( + """ + Less than. If set, value must be less than this. Only applicable to numbers. + """ + ), + ] = None, + le: Annotated[ + Optional[float], + Doc( + """ + Less than or equal. If set, value must be less than or equal to this. + Only applicable to numbers. + """ + ), + ] = None, + min_length: Annotated[ + Optional[int], + Doc( + """ + Minimum length for strings. + """ + ), + ] = None, + max_length: Annotated[ + Optional[int], + Doc( + """ + Maximum length for strings. + """ + ), + ] = None, + pattern: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + ] = None, + regex: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Annotated[ + Union[str, None], + Doc( + """ + Parameter field name for discriminating the type in a tagged union. + """ + ), + ] = None, + strict: Annotated[ + Union[bool, None], + Doc( + """ + If `True`, strict validation is applied to the field. + """ + ), + ] = _Unset, + multiple_of: Annotated[ + Union[float, None], + Doc( + """ + Value must be a multiple of this. Only applicable to numbers. + """ + ), + ] = _Unset, + allow_inf_nan: Annotated[ + Union[bool, None], + Doc( + """ + Allow `inf`, `-inf`, `nan`. Only applicable to numbers. + """ + ), + ] = _Unset, + max_digits: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of allow digits for strings. + """ + ), + ] = _Unset, + decimal_places: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of decimal places allowed for numbers. + """ + ), + ] = _Unset, + examples: Annotated[ + Optional[List[Any]], + Doc( + """ + Example values for this field. + """ + ), + ] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Annotated[ + Optional[Dict[str, Example]], + Doc( + """ + OpenAPI-specific examples. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Swagger UI (that provides the `/docs` interface) has better support for the + OpenAPI-specific examples than the JSON Schema `examples`, that's the main + use case for this. + + Read more about it in the + [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). + """ + ), + ] = None, + deprecated: Annotated[ + Union[deprecated, str, bool, None], + Doc( + """ + Mark this parameter field as deprecated. + + It will affect the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) this parameter field in the generated OpenAPI. + You probably don't need it, but it's available. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = True, + json_schema_extra: Annotated[ + Union[Dict[str, Any], None], + Doc( + """ + Any additional JSON schema data. + """ + ), + ] = None, + **extra: Annotated[ + Any, + Doc( + """ + Include extra fields used by the JSON Schema. + """ + ), + deprecated( + """ + The `extra` kwargs is deprecated. Use `json_schema_extra` instead. + """ + ), + ], +) -> Any: + """ + Declare a path parameter for a *path operation*. + + Read more about it in the + [FastAPI docs for Path Parameters and Numeric Validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/). + + ```python + from typing import Annotated + + from fastapi import FastAPI, Path + + app = FastAPI() + + + @app.get("/items/{item_id}") + async def read_items( + item_id: Annotated[int, Path(title="The ID of the item to get")], + ): + return {"item_id": item_id} + ``` + """ + return params.Path( + default=default, + default_factory=default_factory, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + example=example, + examples=examples, + openapi_examples=openapi_examples, + deprecated=deprecated, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +def Query( # noqa: N802 + default: Annotated[ + Any, + Doc( + """ + Default value if the parameter field is not set. + """ + ), + ] = Undefined, + *, + default_factory: Annotated[ + Union[Callable[[], Any], None], + Doc( + """ + A callable to generate the default value. + + This doesn't affect `Path` parameters as the value is always required. + The parameter is available only for compatibility. + """ + ), + ] = _Unset, + alias: Annotated[ + Optional[str], + Doc( + """ + An alternative name for the parameter field. + + This will be used to extract the data and for the generated OpenAPI. + It is particularly useful when you can't use the name you want because it + is a Python reserved keyword or similar. + """ + ), + ] = None, + alias_priority: Annotated[ + Union[int, None], + Doc( + """ + Priority of the alias. This affects whether an alias generator is used. + """ + ), + ] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Whitelist' validation step. The parameter field will be the single one + allowed by the alias or set of aliases defined. + """ + ), + ] = None, + serialization_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Blacklist' validation step. The vanilla parameter field will be the + single one of the alias' or set of aliases' fields and all the other + fields will be ignored at serialization time. + """ + ), + ] = None, + title: Annotated[ + Optional[str], + Doc( + """ + Human-readable title. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Human-readable description. + """ + ), + ] = None, + gt: Annotated[ + Optional[float], + Doc( + """ + Greater than. If set, value must be greater than this. Only applicable to + numbers. + """ + ), + ] = None, + ge: Annotated[ + Optional[float], + Doc( + """ + Greater than or equal. If set, value must be greater than or equal to + this. Only applicable to numbers. + """ + ), + ] = None, + lt: Annotated[ + Optional[float], + Doc( + """ + Less than. If set, value must be less than this. Only applicable to numbers. + """ + ), + ] = None, + le: Annotated[ + Optional[float], + Doc( + """ + Less than or equal. If set, value must be less than or equal to this. + Only applicable to numbers. + """ + ), + ] = None, + min_length: Annotated[ + Optional[int], + Doc( + """ + Minimum length for strings. + """ + ), + ] = None, + max_length: Annotated[ + Optional[int], + Doc( + """ + Maximum length for strings. + """ + ), + ] = None, + pattern: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + ] = None, + regex: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Annotated[ + Union[str, None], + Doc( + """ + Parameter field name for discriminating the type in a tagged union. + """ + ), + ] = None, + strict: Annotated[ + Union[bool, None], + Doc( + """ + If `True`, strict validation is applied to the field. + """ + ), + ] = _Unset, + multiple_of: Annotated[ + Union[float, None], + Doc( + """ + Value must be a multiple of this. Only applicable to numbers. + """ + ), + ] = _Unset, + allow_inf_nan: Annotated[ + Union[bool, None], + Doc( + """ + Allow `inf`, `-inf`, `nan`. Only applicable to numbers. + """ + ), + ] = _Unset, + max_digits: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of allow digits for strings. + """ + ), + ] = _Unset, + decimal_places: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of decimal places allowed for numbers. + """ + ), + ] = _Unset, + examples: Annotated[ + Optional[List[Any]], + Doc( + """ + Example values for this field. + """ + ), + ] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Annotated[ + Optional[Dict[str, Example]], + Doc( + """ + OpenAPI-specific examples. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Swagger UI (that provides the `/docs` interface) has better support for the + OpenAPI-specific examples than the JSON Schema `examples`, that's the main + use case for this. + + Read more about it in the + [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). + """ + ), + ] = None, + deprecated: Annotated[ + Union[deprecated, str, bool, None], + Doc( + """ + Mark this parameter field as deprecated. + + It will affect the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) this parameter field in the generated OpenAPI. + You probably don't need it, but it's available. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = True, + json_schema_extra: Annotated[ + Union[Dict[str, Any], None], + Doc( + """ + Any additional JSON schema data. + """ + ), + ] = None, + **extra: Annotated[ + Any, + Doc( + """ + Include extra fields used by the JSON Schema. + """ + ), + deprecated( + """ + The `extra` kwargs is deprecated. Use `json_schema_extra` instead. + """ + ), + ], +) -> Any: + return params.Query( + default=default, + default_factory=default_factory, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + example=example, + examples=examples, + openapi_examples=openapi_examples, + deprecated=deprecated, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +def Header( # noqa: N802 + default: Annotated[ + Any, + Doc( + """ + Default value if the parameter field is not set. + """ + ), + ] = Undefined, + *, + default_factory: Annotated[ + Union[Callable[[], Any], None], + Doc( + """ + A callable to generate the default value. + + This doesn't affect `Path` parameters as the value is always required. + The parameter is available only for compatibility. + """ + ), + ] = _Unset, + alias: Annotated[ + Optional[str], + Doc( + """ + An alternative name for the parameter field. + + This will be used to extract the data and for the generated OpenAPI. + It is particularly useful when you can't use the name you want because it + is a Python reserved keyword or similar. + """ + ), + ] = None, + alias_priority: Annotated[ + Union[int, None], + Doc( + """ + Priority of the alias. This affects whether an alias generator is used. + """ + ), + ] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Whitelist' validation step. The parameter field will be the single one + allowed by the alias or set of aliases defined. + """ + ), + ] = None, + serialization_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Blacklist' validation step. The vanilla parameter field will be the + single one of the alias' or set of aliases' fields and all the other + fields will be ignored at serialization time. + """ + ), + ] = None, + convert_underscores: Annotated[ + bool, + Doc( + """ + Automatically convert underscores to hyphens in the parameter field name. + + Read more about it in the + [FastAPI docs for Header Parameters](https://fastapi.tiangolo.com/tutorial/header-params/#automatic-conversion) + """ + ), + ] = True, + title: Annotated[ + Optional[str], + Doc( + """ + Human-readable title. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Human-readable description. + """ + ), + ] = None, + gt: Annotated[ + Optional[float], + Doc( + """ + Greater than. If set, value must be greater than this. Only applicable to + numbers. + """ + ), + ] = None, + ge: Annotated[ + Optional[float], + Doc( + """ + Greater than or equal. If set, value must be greater than or equal to + this. Only applicable to numbers. + """ + ), + ] = None, + lt: Annotated[ + Optional[float], + Doc( + """ + Less than. If set, value must be less than this. Only applicable to numbers. + """ + ), + ] = None, + le: Annotated[ + Optional[float], + Doc( + """ + Less than or equal. If set, value must be less than or equal to this. + Only applicable to numbers. + """ + ), + ] = None, + min_length: Annotated[ + Optional[int], + Doc( + """ + Minimum length for strings. + """ + ), + ] = None, + max_length: Annotated[ + Optional[int], + Doc( + """ + Maximum length for strings. + """ + ), + ] = None, + pattern: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + ] = None, + regex: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Annotated[ + Union[str, None], + Doc( + """ + Parameter field name for discriminating the type in a tagged union. + """ + ), + ] = None, + strict: Annotated[ + Union[bool, None], + Doc( + """ + If `True`, strict validation is applied to the field. + """ + ), + ] = _Unset, + multiple_of: Annotated[ + Union[float, None], + Doc( + """ + Value must be a multiple of this. Only applicable to numbers. + """ + ), + ] = _Unset, + allow_inf_nan: Annotated[ + Union[bool, None], + Doc( + """ + Allow `inf`, `-inf`, `nan`. Only applicable to numbers. + """ + ), + ] = _Unset, + max_digits: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of allow digits for strings. + """ + ), + ] = _Unset, + decimal_places: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of decimal places allowed for numbers. + """ + ), + ] = _Unset, + examples: Annotated[ + Optional[List[Any]], + Doc( + """ + Example values for this field. + """ + ), + ] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Annotated[ + Optional[Dict[str, Example]], + Doc( + """ + OpenAPI-specific examples. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Swagger UI (that provides the `/docs` interface) has better support for the + OpenAPI-specific examples than the JSON Schema `examples`, that's the main + use case for this. + + Read more about it in the + [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). + """ + ), + ] = None, + deprecated: Annotated[ + Union[deprecated, str, bool, None], + Doc( + """ + Mark this parameter field as deprecated. + + It will affect the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) this parameter field in the generated OpenAPI. + You probably don't need it, but it's available. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = True, + json_schema_extra: Annotated[ + Union[Dict[str, Any], None], + Doc( + """ + Any additional JSON schema data. + """ + ), + ] = None, + **extra: Annotated[ + Any, + Doc( + """ + Include extra fields used by the JSON Schema. + """ + ), + deprecated( + """ + The `extra` kwargs is deprecated. Use `json_schema_extra` instead. + """ + ), + ], +) -> Any: + return params.Header( + default=default, + default_factory=default_factory, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + convert_underscores=convert_underscores, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + example=example, + examples=examples, + openapi_examples=openapi_examples, + deprecated=deprecated, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +def Cookie( # noqa: N802 + default: Annotated[ + Any, + Doc( + """ + Default value if the parameter field is not set. + """ + ), + ] = Undefined, + *, + default_factory: Annotated[ + Union[Callable[[], Any], None], + Doc( + """ + A callable to generate the default value. + + This doesn't affect `Path` parameters as the value is always required. + The parameter is available only for compatibility. + """ + ), + ] = _Unset, + alias: Annotated[ + Optional[str], + Doc( + """ + An alternative name for the parameter field. + + This will be used to extract the data and for the generated OpenAPI. + It is particularly useful when you can't use the name you want because it + is a Python reserved keyword or similar. + """ + ), + ] = None, + alias_priority: Annotated[ + Union[int, None], + Doc( + """ + Priority of the alias. This affects whether an alias generator is used. + """ + ), + ] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Whitelist' validation step. The parameter field will be the single one + allowed by the alias or set of aliases defined. + """ + ), + ] = None, + serialization_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Blacklist' validation step. The vanilla parameter field will be the + single one of the alias' or set of aliases' fields and all the other + fields will be ignored at serialization time. + """ + ), + ] = None, + title: Annotated[ + Optional[str], + Doc( + """ + Human-readable title. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Human-readable description. + """ + ), + ] = None, + gt: Annotated[ + Optional[float], + Doc( + """ + Greater than. If set, value must be greater than this. Only applicable to + numbers. + """ + ), + ] = None, + ge: Annotated[ + Optional[float], + Doc( + """ + Greater than or equal. If set, value must be greater than or equal to + this. Only applicable to numbers. + """ + ), + ] = None, + lt: Annotated[ + Optional[float], + Doc( + """ + Less than. If set, value must be less than this. Only applicable to numbers. + """ + ), + ] = None, + le: Annotated[ + Optional[float], + Doc( + """ + Less than or equal. If set, value must be less than or equal to this. + Only applicable to numbers. + """ + ), + ] = None, + min_length: Annotated[ + Optional[int], + Doc( + """ + Minimum length for strings. + """ + ), + ] = None, + max_length: Annotated[ + Optional[int], + Doc( + """ + Maximum length for strings. + """ + ), + ] = None, + pattern: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + ] = None, + regex: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Annotated[ + Union[str, None], + Doc( + """ + Parameter field name for discriminating the type in a tagged union. + """ + ), + ] = None, + strict: Annotated[ + Union[bool, None], + Doc( + """ + If `True`, strict validation is applied to the field. + """ + ), + ] = _Unset, + multiple_of: Annotated[ + Union[float, None], + Doc( + """ + Value must be a multiple of this. Only applicable to numbers. + """ + ), + ] = _Unset, + allow_inf_nan: Annotated[ + Union[bool, None], + Doc( + """ + Allow `inf`, `-inf`, `nan`. Only applicable to numbers. + """ + ), + ] = _Unset, + max_digits: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of allow digits for strings. + """ + ), + ] = _Unset, + decimal_places: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of decimal places allowed for numbers. + """ + ), + ] = _Unset, + examples: Annotated[ + Optional[List[Any]], + Doc( + """ + Example values for this field. + """ + ), + ] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Annotated[ + Optional[Dict[str, Example]], + Doc( + """ + OpenAPI-specific examples. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Swagger UI (that provides the `/docs` interface) has better support for the + OpenAPI-specific examples than the JSON Schema `examples`, that's the main + use case for this. + + Read more about it in the + [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). + """ + ), + ] = None, + deprecated: Annotated[ + Union[deprecated, str, bool, None], + Doc( + """ + Mark this parameter field as deprecated. + + It will affect the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) this parameter field in the generated OpenAPI. + You probably don't need it, but it's available. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = True, + json_schema_extra: Annotated[ + Union[Dict[str, Any], None], + Doc( + """ + Any additional JSON schema data. + """ + ), + ] = None, + **extra: Annotated[ + Any, + Doc( + """ + Include extra fields used by the JSON Schema. + """ + ), + deprecated( + """ + The `extra` kwargs is deprecated. Use `json_schema_extra` instead. + """ + ), + ], +) -> Any: + return params.Cookie( + default=default, + default_factory=default_factory, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + example=example, + examples=examples, + openapi_examples=openapi_examples, + deprecated=deprecated, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +def Body( # noqa: N802 + default: Annotated[ + Any, + Doc( + """ + Default value if the parameter field is not set. + """ + ), + ] = Undefined, + *, + default_factory: Annotated[ + Union[Callable[[], Any], None], + Doc( + """ + A callable to generate the default value. + + This doesn't affect `Path` parameters as the value is always required. + The parameter is available only for compatibility. + """ + ), + ] = _Unset, + embed: Annotated[ + Union[bool, None], + Doc( + """ + When `embed` is `True`, the parameter will be expected in a JSON body as a + key instead of being the JSON body itself. + + This happens automatically when more than one `Body` parameter is declared. + + Read more about it in the + [FastAPI docs for Body - Multiple Parameters](https://fastapi.tiangolo.com/tutorial/body-multiple-params/#embed-a-single-body-parameter). + """ + ), + ] = None, + media_type: Annotated[ + str, + Doc( + """ + The media type of this parameter field. Changing it would affect the + generated OpenAPI, but currently it doesn't affect the parsing of the data. + """ + ), + ] = "application/json", + alias: Annotated[ + Optional[str], + Doc( + """ + An alternative name for the parameter field. + + This will be used to extract the data and for the generated OpenAPI. + It is particularly useful when you can't use the name you want because it + is a Python reserved keyword or similar. + """ + ), + ] = None, + alias_priority: Annotated[ + Union[int, None], + Doc( + """ + Priority of the alias. This affects whether an alias generator is used. + """ + ), + ] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Whitelist' validation step. The parameter field will be the single one + allowed by the alias or set of aliases defined. + """ + ), + ] = None, + serialization_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Blacklist' validation step. The vanilla parameter field will be the + single one of the alias' or set of aliases' fields and all the other + fields will be ignored at serialization time. + """ + ), + ] = None, + title: Annotated[ + Optional[str], + Doc( + """ + Human-readable title. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Human-readable description. + """ + ), + ] = None, + gt: Annotated[ + Optional[float], + Doc( + """ + Greater than. If set, value must be greater than this. Only applicable to + numbers. + """ + ), + ] = None, + ge: Annotated[ + Optional[float], + Doc( + """ + Greater than or equal. If set, value must be greater than or equal to + this. Only applicable to numbers. + """ + ), + ] = None, + lt: Annotated[ + Optional[float], + Doc( + """ + Less than. If set, value must be less than this. Only applicable to numbers. + """ + ), + ] = None, + le: Annotated[ + Optional[float], + Doc( + """ + Less than or equal. If set, value must be less than or equal to this. + Only applicable to numbers. + """ + ), + ] = None, + min_length: Annotated[ + Optional[int], + Doc( + """ + Minimum length for strings. + """ + ), + ] = None, + max_length: Annotated[ + Optional[int], + Doc( + """ + Maximum length for strings. + """ + ), + ] = None, + pattern: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + ] = None, + regex: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Annotated[ + Union[str, None], + Doc( + """ + Parameter field name for discriminating the type in a tagged union. + """ + ), + ] = None, + strict: Annotated[ + Union[bool, None], + Doc( + """ + If `True`, strict validation is applied to the field. + """ + ), + ] = _Unset, + multiple_of: Annotated[ + Union[float, None], + Doc( + """ + Value must be a multiple of this. Only applicable to numbers. + """ + ), + ] = _Unset, + allow_inf_nan: Annotated[ + Union[bool, None], + Doc( + """ + Allow `inf`, `-inf`, `nan`. Only applicable to numbers. + """ + ), + ] = _Unset, + max_digits: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of allow digits for strings. + """ + ), + ] = _Unset, + decimal_places: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of decimal places allowed for numbers. + """ + ), + ] = _Unset, + examples: Annotated[ + Optional[List[Any]], + Doc( + """ + Example values for this field. + """ + ), + ] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Annotated[ + Optional[Dict[str, Example]], + Doc( + """ + OpenAPI-specific examples. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Swagger UI (that provides the `/docs` interface) has better support for the + OpenAPI-specific examples than the JSON Schema `examples`, that's the main + use case for this. + + Read more about it in the + [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). + """ + ), + ] = None, + deprecated: Annotated[ + Union[deprecated, str, bool, None], + Doc( + """ + Mark this parameter field as deprecated. + + It will affect the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) this parameter field in the generated OpenAPI. + You probably don't need it, but it's available. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = True, + json_schema_extra: Annotated[ + Union[Dict[str, Any], None], + Doc( + """ + Any additional JSON schema data. + """ + ), + ] = None, + **extra: Annotated[ + Any, + Doc( + """ + Include extra fields used by the JSON Schema. + """ + ), + deprecated( + """ + The `extra` kwargs is deprecated. Use `json_schema_extra` instead. + """ + ), + ], +) -> Any: + return params.Body( + default=default, + default_factory=default_factory, + embed=embed, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + example=example, + examples=examples, + openapi_examples=openapi_examples, + deprecated=deprecated, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +def Form( # noqa: N802 + default: Annotated[ + Any, + Doc( + """ + Default value if the parameter field is not set. + """ + ), + ] = Undefined, + *, + default_factory: Annotated[ + Union[Callable[[], Any], None], + Doc( + """ + A callable to generate the default value. + + This doesn't affect `Path` parameters as the value is always required. + The parameter is available only for compatibility. + """ + ), + ] = _Unset, + media_type: Annotated[ + str, + Doc( + """ + The media type of this parameter field. Changing it would affect the + generated OpenAPI, but currently it doesn't affect the parsing of the data. + """ + ), + ] = "application/x-www-form-urlencoded", + alias: Annotated[ + Optional[str], + Doc( + """ + An alternative name for the parameter field. + + This will be used to extract the data and for the generated OpenAPI. + It is particularly useful when you can't use the name you want because it + is a Python reserved keyword or similar. + """ + ), + ] = None, + alias_priority: Annotated[ + Union[int, None], + Doc( + """ + Priority of the alias. This affects whether an alias generator is used. + """ + ), + ] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Whitelist' validation step. The parameter field will be the single one + allowed by the alias or set of aliases defined. + """ + ), + ] = None, + serialization_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Blacklist' validation step. The vanilla parameter field will be the + single one of the alias' or set of aliases' fields and all the other + fields will be ignored at serialization time. + """ + ), + ] = None, + title: Annotated[ + Optional[str], + Doc( + """ + Human-readable title. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Human-readable description. + """ + ), + ] = None, + gt: Annotated[ + Optional[float], + Doc( + """ + Greater than. If set, value must be greater than this. Only applicable to + numbers. + """ + ), + ] = None, + ge: Annotated[ + Optional[float], + Doc( + """ + Greater than or equal. If set, value must be greater than or equal to + this. Only applicable to numbers. + """ + ), + ] = None, + lt: Annotated[ + Optional[float], + Doc( + """ + Less than. If set, value must be less than this. Only applicable to numbers. + """ + ), + ] = None, + le: Annotated[ + Optional[float], + Doc( + """ + Less than or equal. If set, value must be less than or equal to this. + Only applicable to numbers. + """ + ), + ] = None, + min_length: Annotated[ + Optional[int], + Doc( + """ + Minimum length for strings. + """ + ), + ] = None, + max_length: Annotated[ + Optional[int], + Doc( + """ + Maximum length for strings. + """ + ), + ] = None, + pattern: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + ] = None, + regex: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Annotated[ + Union[str, None], + Doc( + """ + Parameter field name for discriminating the type in a tagged union. + """ + ), + ] = None, + strict: Annotated[ + Union[bool, None], + Doc( + """ + If `True`, strict validation is applied to the field. + """ + ), + ] = _Unset, + multiple_of: Annotated[ + Union[float, None], + Doc( + """ + Value must be a multiple of this. Only applicable to numbers. + """ + ), + ] = _Unset, + allow_inf_nan: Annotated[ + Union[bool, None], + Doc( + """ + Allow `inf`, `-inf`, `nan`. Only applicable to numbers. + """ + ), + ] = _Unset, + max_digits: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of allow digits for strings. + """ + ), + ] = _Unset, + decimal_places: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of decimal places allowed for numbers. + """ + ), + ] = _Unset, + examples: Annotated[ + Optional[List[Any]], + Doc( + """ + Example values for this field. + """ + ), + ] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Annotated[ + Optional[Dict[str, Example]], + Doc( + """ + OpenAPI-specific examples. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Swagger UI (that provides the `/docs` interface) has better support for the + OpenAPI-specific examples than the JSON Schema `examples`, that's the main + use case for this. + + Read more about it in the + [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). + """ + ), + ] = None, + deprecated: Annotated[ + Union[deprecated, str, bool, None], + Doc( + """ + Mark this parameter field as deprecated. + + It will affect the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) this parameter field in the generated OpenAPI. + You probably don't need it, but it's available. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = True, + json_schema_extra: Annotated[ + Union[Dict[str, Any], None], + Doc( + """ + Any additional JSON schema data. + """ + ), + ] = None, + **extra: Annotated[ + Any, + Doc( + """ + Include extra fields used by the JSON Schema. + """ + ), + deprecated( + """ + The `extra` kwargs is deprecated. Use `json_schema_extra` instead. + """ + ), + ], +) -> Any: + return params.Form( + default=default, + default_factory=default_factory, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + example=example, + examples=examples, + openapi_examples=openapi_examples, + deprecated=deprecated, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +def File( # noqa: N802 + default: Annotated[ + Any, + Doc( + """ + Default value if the parameter field is not set. + """ + ), + ] = Undefined, + *, + default_factory: Annotated[ + Union[Callable[[], Any], None], + Doc( + """ + A callable to generate the default value. + + This doesn't affect `Path` parameters as the value is always required. + The parameter is available only for compatibility. + """ + ), + ] = _Unset, + media_type: Annotated[ + str, + Doc( + """ + The media type of this parameter field. Changing it would affect the + generated OpenAPI, but currently it doesn't affect the parsing of the data. + """ + ), + ] = "multipart/form-data", + alias: Annotated[ + Optional[str], + Doc( + """ + An alternative name for the parameter field. + + This will be used to extract the data and for the generated OpenAPI. + It is particularly useful when you can't use the name you want because it + is a Python reserved keyword or similar. + """ + ), + ] = None, + alias_priority: Annotated[ + Union[int, None], + Doc( + """ + Priority of the alias. This affects whether an alias generator is used. + """ + ), + ] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Whitelist' validation step. The parameter field will be the single one + allowed by the alias or set of aliases defined. + """ + ), + ] = None, + serialization_alias: Annotated[ + Union[str, None], + Doc( + """ + 'Blacklist' validation step. The vanilla parameter field will be the + single one of the alias' or set of aliases' fields and all the other + fields will be ignored at serialization time. + """ + ), + ] = None, + title: Annotated[ + Optional[str], + Doc( + """ + Human-readable title. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Human-readable description. + """ + ), + ] = None, + gt: Annotated[ + Optional[float], + Doc( + """ + Greater than. If set, value must be greater than this. Only applicable to + numbers. + """ + ), + ] = None, + ge: Annotated[ + Optional[float], + Doc( + """ + Greater than or equal. If set, value must be greater than or equal to + this. Only applicable to numbers. + """ + ), + ] = None, + lt: Annotated[ + Optional[float], + Doc( + """ + Less than. If set, value must be less than this. Only applicable to numbers. + """ + ), + ] = None, + le: Annotated[ + Optional[float], + Doc( + """ + Less than or equal. If set, value must be less than or equal to this. + Only applicable to numbers. + """ + ), + ] = None, + min_length: Annotated[ + Optional[int], + Doc( + """ + Minimum length for strings. + """ + ), + ] = None, + max_length: Annotated[ + Optional[int], + Doc( + """ + Maximum length for strings. + """ + ), + ] = None, + pattern: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + ] = None, + regex: Annotated[ + Optional[str], + Doc( + """ + RegEx pattern for strings. + """ + ), + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Annotated[ + Union[str, None], + Doc( + """ + Parameter field name for discriminating the type in a tagged union. + """ + ), + ] = None, + strict: Annotated[ + Union[bool, None], + Doc( + """ + If `True`, strict validation is applied to the field. + """ + ), + ] = _Unset, + multiple_of: Annotated[ + Union[float, None], + Doc( + """ + Value must be a multiple of this. Only applicable to numbers. + """ + ), + ] = _Unset, + allow_inf_nan: Annotated[ + Union[bool, None], + Doc( + """ + Allow `inf`, `-inf`, `nan`. Only applicable to numbers. + """ + ), + ] = _Unset, + max_digits: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of allow digits for strings. + """ + ), + ] = _Unset, + decimal_places: Annotated[ + Union[int, None], + Doc( + """ + Maximum number of decimal places allowed for numbers. + """ + ), + ] = _Unset, + examples: Annotated[ + Optional[List[Any]], + Doc( + """ + Example values for this field. + """ + ), + ] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Annotated[ + Optional[Dict[str, Example]], + Doc( + """ + OpenAPI-specific examples. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Swagger UI (that provides the `/docs` interface) has better support for the + OpenAPI-specific examples than the JSON Schema `examples`, that's the main + use case for this. + + Read more about it in the + [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). + """ + ), + ] = None, + deprecated: Annotated[ + Union[deprecated, str, bool, None], + Doc( + """ + Mark this parameter field as deprecated. + + It will affect the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) this parameter field in the generated OpenAPI. + You probably don't need it, but it's available. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = True, + json_schema_extra: Annotated[ + Union[Dict[str, Any], None], + Doc( + """ + Any additional JSON schema data. + """ + ), + ] = None, + **extra: Annotated[ + Any, + Doc( + """ + Include extra fields used by the JSON Schema. + """ + ), + deprecated( + """ + The `extra` kwargs is deprecated. Use `json_schema_extra` instead. + """ + ), + ], +) -> Any: + return params.File( + default=default, + default_factory=default_factory, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + example=example, + examples=examples, + openapi_examples=openapi_examples, + deprecated=deprecated, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +def Depends( # noqa: N802 + dependency: Annotated[ + Optional[Callable[..., Any]], + Doc( + """ + A "dependable" callable (like a function). + + Don't call it directly, FastAPI will call it for you, just pass the object + directly. + """ + ), + ] = None, + *, + use_cache: Annotated[ + bool, + Doc( + """ + By default, after a dependency is called the first time in a request, if + the dependency is declared again for the rest of the request (for example + if the dependency is needed by several dependencies), the value will be + re-used for the rest of the request. + + Set `use_cache` to `False` to disable this behavior and ensure the + dependency is called again (if declared more than once) in the same request. + """ + ), + ] = True, +) -> Any: + """ + Declare a FastAPI dependency. + + It takes a single "dependable" callable (like a function). + + Don't call it directly, FastAPI will call it for you. + + Read more about it in the + [FastAPI docs for Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/). + + **Example** + + ```python + from typing import Annotated + + from fastapi import Depends, FastAPI + + app = FastAPI() + + + async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100): + return {"q": q, "skip": skip, "limit": limit} + + + @app.get("/items/") + async def read_items(commons: Annotated[dict, Depends(common_parameters)]): + return commons + ``` + """ + return params.Depends(dependency=dependency, use_cache=use_cache) + + +def Security( # noqa: N802 + dependency: Annotated[ + Optional[Callable[..., Any]], + Doc( + """ + A "dependable" callable (like a function). + + Don't call it directly, FastAPI will call it for you, just pass the object + directly. + """ + ), + ] = None, + *, + scopes: Annotated[ + Optional[Sequence[str]], + Doc( + """ + OAuth2 scopes required for the *path operation* that uses this Security + dependency. + + The term "scope" comes from the OAuth2 specification, it seems to be + intentionally vague and interpretable. It normally refers to permissions, + in cases to roles. + + These scopes are integrated with OpenAPI (and the API docs at `/docs`). + So they are visible in the OpenAPI specification. + ) + """ + ), + ] = None, + use_cache: Annotated[ + bool, + Doc( + """ + By default, after a dependency is called the first time in a request, if + the dependency is declared again for the rest of the request (for example + if the dependency is needed by several dependencies), the value will be + re-used for the rest of the request. + + Set `use_cache` to `False` to disable this behavior and ensure the + dependency is called again (if declared more than once) in the same request. + """ + ), + ] = True, +) -> Any: + """ + Declare a FastAPI Security dependency. + + The only difference with a regular dependency is that it can declare OAuth2 + scopes that will be integrated with OpenAPI and the automatic UI docs (by default + at `/docs`). + + It takes a single "dependable" callable (like a function). + + Don't call it directly, FastAPI will call it for you. + + Read more about it in the + [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/) and + in the + [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/). + + **Example** + + ```python + from typing import Annotated + + from fastapi import Security, FastAPI + + from .db import User + from .security import get_current_active_user + + app = FastAPI() + + @app.get("/users/me/items/") + async def read_own_items( + current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])] + ): + return [{"item_id": "Foo", "owner": current_user.username}] + ``` + """ + return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache) diff --git a/.venv/Lib/site-packages/fastapi/params.py b/.venv/Lib/site-packages/fastapi/params.py new file mode 100644 index 0000000..8f5601d --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/params.py @@ -0,0 +1,786 @@ +import warnings +from enum import Enum +from typing import Any, Callable, Dict, List, Optional, Sequence, Union + +from fastapi.openapi.models import Example +from pydantic.fields import FieldInfo +from typing_extensions import Annotated, deprecated + +from ._compat import ( + PYDANTIC_V2, + PYDANTIC_VERSION_MINOR_TUPLE, + Undefined, +) + +_Unset: Any = Undefined + + +class ParamTypes(Enum): + query = "query" + header = "header" + path = "path" + cookie = "cookie" + + +class Param(FieldInfo): + in_: ParamTypes + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + if example is not _Unset: + warnings.warn( + "`example` has been deprecated, please use `examples` instead", + category=DeprecationWarning, + stacklevel=4, + ) + self.example = example + self.include_in_schema = include_in_schema + self.openapi_examples = openapi_examples + kwargs = dict( + default=default, + default_factory=default_factory, + alias=alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + discriminator=discriminator, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + **extra, + ) + if examples is not None: + kwargs["examples"] = examples + if regex is not None: + warnings.warn( + "`regex` has been deprecated, please use `pattern` instead", + category=DeprecationWarning, + stacklevel=4, + ) + current_json_schema_extra = json_schema_extra or extra + if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): + self.deprecated = deprecated + else: + kwargs["deprecated"] = deprecated + if PYDANTIC_V2: + kwargs.update( + { + "annotation": annotation, + "alias_priority": alias_priority, + "validation_alias": validation_alias, + "serialization_alias": serialization_alias, + "strict": strict, + "json_schema_extra": current_json_schema_extra, + } + ) + kwargs["pattern"] = pattern or regex + else: + kwargs["regex"] = pattern or regex + kwargs.update(**current_json_schema_extra) + use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} + + super().__init__(**use_kwargs) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.default})" + + +class Path(Param): + in_ = ParamTypes.path + + def __init__( + self, + default: Any = ..., + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + assert default is ..., "Path parameters cannot have a default value" + self.in_ = self.in_ + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Query(Param): + in_ = ParamTypes.query + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Header(Param): + in_ = ParamTypes.header + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + convert_underscores: bool = True, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + self.convert_underscores = convert_underscores + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Cookie(Param): + in_ = ParamTypes.cookie + + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Body(FieldInfo): + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + embed: Union[bool, None] = None, + media_type: str = "application/json", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + self.embed = embed + self.media_type = media_type + if example is not _Unset: + warnings.warn( + "`example` has been deprecated, please use `examples` instead", + category=DeprecationWarning, + stacklevel=4, + ) + self.example = example + self.include_in_schema = include_in_schema + self.openapi_examples = openapi_examples + kwargs = dict( + default=default, + default_factory=default_factory, + alias=alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + discriminator=discriminator, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + **extra, + ) + if examples is not None: + kwargs["examples"] = examples + if regex is not None: + warnings.warn( + "`regex` has been deprecated, please use `pattern` instead", + category=DeprecationWarning, + stacklevel=4, + ) + current_json_schema_extra = json_schema_extra or extra + if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): + self.deprecated = deprecated + else: + kwargs["deprecated"] = deprecated + if PYDANTIC_V2: + kwargs.update( + { + "annotation": annotation, + "alias_priority": alias_priority, + "validation_alias": validation_alias, + "serialization_alias": serialization_alias, + "strict": strict, + "json_schema_extra": current_json_schema_extra, + } + ) + kwargs["pattern"] = pattern or regex + else: + kwargs["regex"] = pattern or regex + kwargs.update(**current_json_schema_extra) + + use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} + + super().__init__(**use_kwargs) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.default})" + + +class Form(Body): + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + media_type: str = "application/x-www-form-urlencoded", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class File(Form): + def __init__( + self, + default: Any = Undefined, + *, + default_factory: Union[Callable[[], Any], None] = _Unset, + annotation: Optional[Any] = None, + media_type: str = "multipart/form-data", + alias: Optional[str] = None, + alias_priority: Union[int, None] = _Unset, + # TODO: update when deprecating Pydantic v1, import these types + # validation_alias: str | AliasPath | AliasChoices | None + validation_alias: Union[str, None] = None, + serialization_alias: Union[str, None] = None, + title: Optional[str] = None, + description: Optional[str] = None, + gt: Optional[float] = None, + ge: Optional[float] = None, + lt: Optional[float] = None, + le: Optional[float] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + pattern: Optional[str] = None, + regex: Annotated[ + Optional[str], + deprecated( + "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." + ), + ] = None, + discriminator: Union[str, None] = None, + strict: Union[bool, None] = _Unset, + multiple_of: Union[float, None] = _Unset, + allow_inf_nan: Union[bool, None] = _Unset, + max_digits: Union[int, None] = _Unset, + decimal_places: Union[int, None] = _Unset, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = _Unset, + openapi_examples: Optional[Dict[str, Example]] = None, + deprecated: Union[deprecated, str, bool, None] = None, + include_in_schema: bool = True, + json_schema_extra: Union[Dict[str, Any], None] = None, + **extra: Any, + ): + super().__init__( + default=default, + default_factory=default_factory, + annotation=annotation, + media_type=media_type, + alias=alias, + alias_priority=alias_priority, + validation_alias=validation_alias, + serialization_alias=serialization_alias, + title=title, + description=description, + gt=gt, + ge=ge, + lt=lt, + le=le, + min_length=min_length, + max_length=max_length, + pattern=pattern, + regex=regex, + discriminator=discriminator, + strict=strict, + multiple_of=multiple_of, + allow_inf_nan=allow_inf_nan, + max_digits=max_digits, + decimal_places=decimal_places, + deprecated=deprecated, + example=example, + examples=examples, + openapi_examples=openapi_examples, + include_in_schema=include_in_schema, + json_schema_extra=json_schema_extra, + **extra, + ) + + +class Depends: + def __init__( + self, dependency: Optional[Callable[..., Any]] = None, *, use_cache: bool = True + ): + self.dependency = dependency + self.use_cache = use_cache + + def __repr__(self) -> str: + attr = getattr(self.dependency, "__name__", type(self.dependency).__name__) + cache = "" if self.use_cache else ", use_cache=False" + return f"{self.__class__.__name__}({attr}{cache})" + + +class Security(Depends): + def __init__( + self, + dependency: Optional[Callable[..., Any]] = None, + *, + scopes: Optional[Sequence[str]] = None, + use_cache: bool = True, + ): + super().__init__(dependency=dependency, use_cache=use_cache) + self.scopes = scopes or [] diff --git a/.venv/Lib/site-packages/fastapi/py.typed b/.venv/Lib/site-packages/fastapi/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/fastapi/requests.py b/.venv/Lib/site-packages/fastapi/requests.py new file mode 100644 index 0000000..d16552c --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/requests.py @@ -0,0 +1,2 @@ +from starlette.requests import HTTPConnection as HTTPConnection # noqa: F401 +from starlette.requests import Request as Request # noqa: F401 diff --git a/.venv/Lib/site-packages/fastapi/responses.py b/.venv/Lib/site-packages/fastapi/responses.py new file mode 100644 index 0000000..6c8db6f --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/responses.py @@ -0,0 +1,48 @@ +from typing import Any + +from starlette.responses import FileResponse as FileResponse # noqa +from starlette.responses import HTMLResponse as HTMLResponse # noqa +from starlette.responses import JSONResponse as JSONResponse # noqa +from starlette.responses import PlainTextResponse as PlainTextResponse # noqa +from starlette.responses import RedirectResponse as RedirectResponse # noqa +from starlette.responses import Response as Response # noqa +from starlette.responses import StreamingResponse as StreamingResponse # noqa + +try: + import ujson +except ImportError: # pragma: nocover + ujson = None # type: ignore + + +try: + import orjson +except ImportError: # pragma: nocover + orjson = None # type: ignore + + +class UJSONResponse(JSONResponse): + """ + JSON response using the high-performance ujson library to serialize data to JSON. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/). + """ + + def render(self, content: Any) -> bytes: + assert ujson is not None, "ujson must be installed to use UJSONResponse" + return ujson.dumps(content, ensure_ascii=False).encode("utf-8") + + +class ORJSONResponse(JSONResponse): + """ + JSON response using the high-performance orjson library to serialize data to JSON. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/). + """ + + def render(self, content: Any) -> bytes: + assert orjson is not None, "orjson must be installed to use ORJSONResponse" + return orjson.dumps( + content, option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY + ) diff --git a/.venv/Lib/site-packages/fastapi/routing.py b/.venv/Lib/site-packages/fastapi/routing.py new file mode 100644 index 0000000..457481e --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/routing.py @@ -0,0 +1,4439 @@ +import asyncio +import dataclasses +import email.message +import inspect +import json +from contextlib import AsyncExitStack, asynccontextmanager +from enum import Enum, IntEnum +from typing import ( + Any, + AsyncIterator, + Callable, + Coroutine, + Dict, + List, + Mapping, + Optional, + Sequence, + Set, + Tuple, + Type, + Union, +) + +from fastapi import params +from fastapi._compat import ( + ModelField, + Undefined, + _get_model_config, + _model_dump, + _normalize_errors, + lenient_issubclass, +) +from fastapi.datastructures import Default, DefaultPlaceholder +from fastapi.dependencies.models import Dependant +from fastapi.dependencies.utils import ( + _should_embed_body_fields, + get_body_field, + get_dependant, + get_flat_dependant, + get_parameterless_sub_dependant, + get_typed_return_annotation, + solve_dependencies, +) +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import ( + FastAPIError, + RequestValidationError, + ResponseValidationError, + WebSocketRequestValidationError, +) +from fastapi.types import DecoratedCallable, IncEx +from fastapi.utils import ( + create_cloned_field, + create_model_field, + generate_unique_id, + get_value_or_default, + is_body_allowed_for_status_code, +) +from pydantic import BaseModel +from starlette import routing +from starlette.concurrency import run_in_threadpool +from starlette.exceptions import HTTPException +from starlette.requests import Request +from starlette.responses import JSONResponse, Response +from starlette.routing import ( + BaseRoute, + Match, + compile_path, + get_name, + request_response, + websocket_session, +) +from starlette.routing import Mount as Mount # noqa +from starlette.types import AppType, ASGIApp, Lifespan, Scope +from starlette.websockets import WebSocket +from typing_extensions import Annotated, Doc, deprecated + + +def _prepare_response_content( + res: Any, + *, + exclude_unset: bool, + exclude_defaults: bool = False, + exclude_none: bool = False, +) -> Any: + if isinstance(res, BaseModel): + read_with_orm_mode = getattr(_get_model_config(res), "read_with_orm_mode", None) + if read_with_orm_mode: + # Let from_orm extract the data from this model instead of converting + # it now to a dict. + # Otherwise, there's no way to extract lazy data that requires attribute + # access instead of dict iteration, e.g. lazy relationships. + return res + return _model_dump( + res, + by_alias=True, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + elif isinstance(res, list): + return [ + _prepare_response_content( + item, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + for item in res + ] + elif isinstance(res, dict): + return { + k: _prepare_response_content( + v, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + for k, v in res.items() + } + elif dataclasses.is_dataclass(res): + return dataclasses.asdict(res) + return res + + +def _merge_lifespan_context( + original_context: Lifespan[Any], nested_context: Lifespan[Any] +) -> Lifespan[Any]: + @asynccontextmanager + async def merged_lifespan( + app: AppType, + ) -> AsyncIterator[Optional[Mapping[str, Any]]]: + async with original_context(app) as maybe_original_state: + async with nested_context(app) as maybe_nested_state: + if maybe_nested_state is None and maybe_original_state is None: + yield None # old ASGI compatibility + else: + yield {**(maybe_nested_state or {}), **(maybe_original_state or {})} + + return merged_lifespan # type: ignore[return-value] + + +async def serialize_response( + *, + field: Optional[ModelField] = None, + response_content: Any, + include: Optional[IncEx] = None, + exclude: Optional[IncEx] = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + is_coroutine: bool = True, +) -> Any: + if field: + errors = [] + if not hasattr(field, "serialize"): + # pydantic v1 + response_content = _prepare_response_content( + response_content, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + if is_coroutine: + value, errors_ = field.validate(response_content, {}, loc=("response",)) + else: + value, errors_ = await run_in_threadpool( + field.validate, response_content, {}, loc=("response",) + ) + if isinstance(errors_, list): + errors.extend(errors_) + elif errors_: + errors.append(errors_) + if errors: + raise ResponseValidationError( + errors=_normalize_errors(errors), body=response_content + ) + + if hasattr(field, "serialize"): + return field.serialize( + value, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + + return jsonable_encoder( + value, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + else: + return jsonable_encoder(response_content) + + +async def run_endpoint_function( + *, dependant: Dependant, values: Dict[str, Any], is_coroutine: bool +) -> Any: + # Only called by get_request_handler. Has been split into its own function to + # facilitate profiling endpoints, since inner functions are harder to profile. + assert dependant.call is not None, "dependant.call must be a function" + + if is_coroutine: + return await dependant.call(**values) + else: + return await run_in_threadpool(dependant.call, **values) + + +def get_request_handler( + dependant: Dependant, + body_field: Optional[ModelField] = None, + status_code: Optional[int] = None, + response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse), + response_field: Optional[ModelField] = None, + response_model_include: Optional[IncEx] = None, + response_model_exclude: Optional[IncEx] = None, + response_model_by_alias: bool = True, + response_model_exclude_unset: bool = False, + response_model_exclude_defaults: bool = False, + response_model_exclude_none: bool = False, + dependency_overrides_provider: Optional[Any] = None, + embed_body_fields: bool = False, +) -> Callable[[Request], Coroutine[Any, Any, Response]]: + assert dependant.call is not None, "dependant.call must be a function" + is_coroutine = asyncio.iscoroutinefunction(dependant.call) + is_body_form = body_field and isinstance(body_field.field_info, params.Form) + if isinstance(response_class, DefaultPlaceholder): + actual_response_class: Type[Response] = response_class.value + else: + actual_response_class = response_class + + async def app(request: Request) -> Response: + response: Union[Response, None] = None + async with AsyncExitStack() as file_stack: + try: + body: Any = None + if body_field: + if is_body_form: + body = await request.form() + file_stack.push_async_callback(body.close) + else: + body_bytes = await request.body() + if body_bytes: + json_body: Any = Undefined + content_type_value = request.headers.get("content-type") + if not content_type_value: + json_body = await request.json() + else: + message = email.message.Message() + message["content-type"] = content_type_value + if message.get_content_maintype() == "application": + subtype = message.get_content_subtype() + if subtype == "json" or subtype.endswith("+json"): + json_body = await request.json() + if json_body != Undefined: + body = json_body + else: + body = body_bytes + except json.JSONDecodeError as e: + validation_error = RequestValidationError( + [ + { + "type": "json_invalid", + "loc": ("body", e.pos), + "msg": "JSON decode error", + "input": {}, + "ctx": {"error": e.msg}, + } + ], + body=e.doc, + ) + raise validation_error from e + except HTTPException: + # If a middleware raises an HTTPException, it should be raised again + raise + except Exception as e: + http_error = HTTPException( + status_code=400, detail="There was an error parsing the body" + ) + raise http_error from e + errors: List[Any] = [] + async with AsyncExitStack() as async_exit_stack: + solved_result = await solve_dependencies( + request=request, + dependant=dependant, + body=body, + dependency_overrides_provider=dependency_overrides_provider, + async_exit_stack=async_exit_stack, + embed_body_fields=embed_body_fields, + ) + errors = solved_result.errors + if not errors: + raw_response = await run_endpoint_function( + dependant=dependant, + values=solved_result.values, + is_coroutine=is_coroutine, + ) + if isinstance(raw_response, Response): + if raw_response.background is None: + raw_response.background = solved_result.background_tasks + response = raw_response + else: + response_args: Dict[str, Any] = { + "background": solved_result.background_tasks + } + # If status_code was set, use it, otherwise use the default from the + # response class, in the case of redirect it's 307 + current_status_code = ( + status_code + if status_code + else solved_result.response.status_code + ) + if current_status_code is not None: + response_args["status_code"] = current_status_code + if solved_result.response.status_code: + response_args["status_code"] = ( + solved_result.response.status_code + ) + content = await serialize_response( + field=response_field, + response_content=raw_response, + include=response_model_include, + exclude=response_model_exclude, + by_alias=response_model_by_alias, + exclude_unset=response_model_exclude_unset, + exclude_defaults=response_model_exclude_defaults, + exclude_none=response_model_exclude_none, + is_coroutine=is_coroutine, + ) + response = actual_response_class(content, **response_args) + if not is_body_allowed_for_status_code(response.status_code): + response.body = b"" + response.headers.raw.extend(solved_result.response.headers.raw) + if errors: + validation_error = RequestValidationError( + _normalize_errors(errors), body=body + ) + raise validation_error + if response is None: + raise FastAPIError( + "No response object was returned. There's a high chance that the " + "application code is raising an exception and a dependency with yield " + "has a block with a bare except, or a block with except Exception, " + "and is not raising the exception again. Read more about it in the " + "docs: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-except" + ) + return response + + return app + + +def get_websocket_app( + dependant: Dependant, + dependency_overrides_provider: Optional[Any] = None, + embed_body_fields: bool = False, +) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]: + async def app(websocket: WebSocket) -> None: + async with AsyncExitStack() as async_exit_stack: + # TODO: remove this scope later, after a few releases + # This scope fastapi_astack is no longer used by FastAPI, kept for + # compatibility, just in case + websocket.scope["fastapi_astack"] = async_exit_stack + solved_result = await solve_dependencies( + request=websocket, + dependant=dependant, + dependency_overrides_provider=dependency_overrides_provider, + async_exit_stack=async_exit_stack, + embed_body_fields=embed_body_fields, + ) + if solved_result.errors: + raise WebSocketRequestValidationError( + _normalize_errors(solved_result.errors) + ) + assert dependant.call is not None, "dependant.call must be a function" + await dependant.call(**solved_result.values) + + return app + + +class APIWebSocketRoute(routing.WebSocketRoute): + def __init__( + self, + path: str, + endpoint: Callable[..., Any], + *, + name: Optional[str] = None, + dependencies: Optional[Sequence[params.Depends]] = None, + dependency_overrides_provider: Optional[Any] = None, + ) -> None: + self.path = path + self.endpoint = endpoint + self.name = get_name(endpoint) if name is None else name + self.dependencies = list(dependencies or []) + self.path_regex, self.path_format, self.param_convertors = compile_path(path) + self.dependant = get_dependant(path=self.path_format, call=self.endpoint) + for depends in self.dependencies[::-1]: + self.dependant.dependencies.insert( + 0, + get_parameterless_sub_dependant(depends=depends, path=self.path_format), + ) + self._flat_dependant = get_flat_dependant(self.dependant) + self._embed_body_fields = _should_embed_body_fields( + self._flat_dependant.body_params + ) + self.app = websocket_session( + get_websocket_app( + dependant=self.dependant, + dependency_overrides_provider=dependency_overrides_provider, + embed_body_fields=self._embed_body_fields, + ) + ) + + def matches(self, scope: Scope) -> Tuple[Match, Scope]: + match, child_scope = super().matches(scope) + if match != Match.NONE: + child_scope["route"] = self + return match, child_scope + + +class APIRoute(routing.Route): + def __init__( + self, + path: str, + endpoint: Callable[..., Any], + *, + response_model: Any = Default(None), + status_code: Optional[int] = None, + tags: Optional[List[Union[str, Enum]]] = None, + dependencies: Optional[Sequence[params.Depends]] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + response_description: str = "Successful Response", + responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + deprecated: Optional[bool] = None, + name: Optional[str] = None, + methods: Optional[Union[Set[str], List[str]]] = None, + operation_id: Optional[str] = None, + response_model_include: Optional[IncEx] = None, + response_model_exclude: Optional[IncEx] = None, + response_model_by_alias: bool = True, + response_model_exclude_unset: bool = False, + response_model_exclude_defaults: bool = False, + response_model_exclude_none: bool = False, + include_in_schema: bool = True, + response_class: Union[Type[Response], DefaultPlaceholder] = Default( + JSONResponse + ), + dependency_overrides_provider: Optional[Any] = None, + callbacks: Optional[List[BaseRoute]] = None, + openapi_extra: Optional[Dict[str, Any]] = None, + generate_unique_id_function: Union[ + Callable[["APIRoute"], str], DefaultPlaceholder + ] = Default(generate_unique_id), + ) -> None: + self.path = path + self.endpoint = endpoint + if isinstance(response_model, DefaultPlaceholder): + return_annotation = get_typed_return_annotation(endpoint) + if lenient_issubclass(return_annotation, Response): + response_model = None + else: + response_model = return_annotation + self.response_model = response_model + self.summary = summary + self.response_description = response_description + self.deprecated = deprecated + self.operation_id = operation_id + self.response_model_include = response_model_include + self.response_model_exclude = response_model_exclude + self.response_model_by_alias = response_model_by_alias + self.response_model_exclude_unset = response_model_exclude_unset + self.response_model_exclude_defaults = response_model_exclude_defaults + self.response_model_exclude_none = response_model_exclude_none + self.include_in_schema = include_in_schema + self.response_class = response_class + self.dependency_overrides_provider = dependency_overrides_provider + self.callbacks = callbacks + self.openapi_extra = openapi_extra + self.generate_unique_id_function = generate_unique_id_function + self.tags = tags or [] + self.responses = responses or {} + self.name = get_name(endpoint) if name is None else name + self.path_regex, self.path_format, self.param_convertors = compile_path(path) + if methods is None: + methods = ["GET"] + self.methods: Set[str] = {method.upper() for method in methods} + if isinstance(generate_unique_id_function, DefaultPlaceholder): + current_generate_unique_id: Callable[[APIRoute], str] = ( + generate_unique_id_function.value + ) + else: + current_generate_unique_id = generate_unique_id_function + self.unique_id = self.operation_id or current_generate_unique_id(self) + # normalize enums e.g. http.HTTPStatus + if isinstance(status_code, IntEnum): + status_code = int(status_code) + self.status_code = status_code + if self.response_model: + assert is_body_allowed_for_status_code(status_code), ( + f"Status code {status_code} must not have a response body" + ) + response_name = "Response_" + self.unique_id + self.response_field = create_model_field( + name=response_name, + type_=self.response_model, + mode="serialization", + ) + # Create a clone of the field, so that a Pydantic submodel is not returned + # as is just because it's an instance of a subclass of a more limited class + # e.g. UserInDB (containing hashed_password) could be a subclass of User + # that doesn't have the hashed_password. But because it's a subclass, it + # would pass the validation and be returned as is. + # By being a new field, no inheritance will be passed as is. A new model + # will always be created. + # TODO: remove when deprecating Pydantic v1 + self.secure_cloned_response_field: Optional[ModelField] = ( + create_cloned_field(self.response_field) + ) + else: + self.response_field = None # type: ignore + self.secure_cloned_response_field = None + self.dependencies = list(dependencies or []) + self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "") + # if a "form feed" character (page break) is found in the description text, + # truncate description text to the content preceding the first "form feed" + self.description = self.description.split("\f")[0].strip() + response_fields = {} + for additional_status_code, response in self.responses.items(): + assert isinstance(response, dict), "An additional response must be a dict" + model = response.get("model") + if model: + assert is_body_allowed_for_status_code(additional_status_code), ( + f"Status code {additional_status_code} must not have a response body" + ) + response_name = f"Response_{additional_status_code}_{self.unique_id}" + response_field = create_model_field( + name=response_name, type_=model, mode="serialization" + ) + response_fields[additional_status_code] = response_field + if response_fields: + self.response_fields: Dict[Union[int, str], ModelField] = response_fields + else: + self.response_fields = {} + + assert callable(endpoint), "An endpoint must be a callable" + self.dependant = get_dependant(path=self.path_format, call=self.endpoint) + for depends in self.dependencies[::-1]: + self.dependant.dependencies.insert( + 0, + get_parameterless_sub_dependant(depends=depends, path=self.path_format), + ) + self._flat_dependant = get_flat_dependant(self.dependant) + self._embed_body_fields = _should_embed_body_fields( + self._flat_dependant.body_params + ) + self.body_field = get_body_field( + flat_dependant=self._flat_dependant, + name=self.unique_id, + embed_body_fields=self._embed_body_fields, + ) + self.app = request_response(self.get_route_handler()) + + def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]: + return get_request_handler( + dependant=self.dependant, + body_field=self.body_field, + status_code=self.status_code, + response_class=self.response_class, + response_field=self.secure_cloned_response_field, + response_model_include=self.response_model_include, + response_model_exclude=self.response_model_exclude, + response_model_by_alias=self.response_model_by_alias, + response_model_exclude_unset=self.response_model_exclude_unset, + response_model_exclude_defaults=self.response_model_exclude_defaults, + response_model_exclude_none=self.response_model_exclude_none, + dependency_overrides_provider=self.dependency_overrides_provider, + embed_body_fields=self._embed_body_fields, + ) + + def matches(self, scope: Scope) -> Tuple[Match, Scope]: + match, child_scope = super().matches(scope) + if match != Match.NONE: + child_scope["route"] = self + return match, child_scope + + +class APIRouter(routing.Router): + """ + `APIRouter` class, used to group *path operations*, for example to structure + an app in multiple files. It would then be included in the `FastAPI` app, or + in another `APIRouter` (ultimately included in the app). + + Read more about it in the + [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/). + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + + app = FastAPI() + router = APIRouter() + + + @router.get("/users/", tags=["users"]) + async def read_users(): + return [{"username": "Rick"}, {"username": "Morty"}] + + + app.include_router(router) + ``` + """ + + def __init__( + self, + *, + prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to all the *path operations* in this + router. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to all the + *path operations* in this router. + + Read more about it in the + [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). + """ + ), + ] = None, + default_response_class: Annotated[ + Type[Response], + Doc( + """ + The default response class to be used. + + Read more in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). + """ + ), + ] = Default(JSONResponse), + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses to be shown in OpenAPI. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/). + + And in the + [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + OpenAPI callbacks that should apply to all *path operations* in this + router. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + routes: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + **Note**: you probably shouldn't use this parameter, it is inherited + from Starlette and supported for compatibility. + + --- + + A list of routes to serve incoming HTTP and WebSocket requests. + """ + ), + deprecated( + """ + You normally wouldn't use this parameter with FastAPI, it is inherited + from Starlette and supported for compatibility. + + In FastAPI, you normally would use the *path operation methods*, + like `router.get()`, `router.post()`, etc. + """ + ), + ] = None, + redirect_slashes: Annotated[ + bool, + Doc( + """ + Whether to detect and redirect slashes in URLs when the client doesn't + use the same format. + """ + ), + ] = True, + default: Annotated[ + Optional[ASGIApp], + Doc( + """ + Default function handler for this router. Used to handle + 404 Not Found errors. + """ + ), + ] = None, + dependency_overrides_provider: Annotated[ + Optional[Any], + Doc( + """ + Only used internally by FastAPI to handle dependency overrides. + + You shouldn't need to use it. It normally points to the `FastAPI` app + object. + """ + ), + ] = None, + route_class: Annotated[ + Type[APIRoute], + Doc( + """ + Custom route (*path operation*) class to be used by this router. + + Read more about it in the + [FastAPI docs for Custom Request and APIRoute class](https://fastapi.tiangolo.com/how-to/custom-request-and-route/#custom-apiroute-class-in-a-router). + """ + ), + ] = APIRoute, + on_startup: Annotated[ + Optional[Sequence[Callable[[], Any]]], + Doc( + """ + A list of startup event handler functions. + + You should instead use the `lifespan` handlers. + + Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). + """ + ), + ] = None, + on_shutdown: Annotated[ + Optional[Sequence[Callable[[], Any]]], + Doc( + """ + A list of shutdown event handler functions. + + You should instead use the `lifespan` handlers. + + Read more in the + [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). + """ + ), + ] = None, + # the generic to Lifespan[AppType] is the type of the top level application + # which the router cannot know statically, so we use typing.Any + lifespan: Annotated[ + Optional[Lifespan[Any]], + Doc( + """ + A `Lifespan` context manager handler. This replaces `startup` and + `shutdown` functions with a single context manager. + + Read more in the + [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark all *path operations* in this router as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + To include (or not) all the *path operations* in this router in the + generated OpenAPI. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> None: + super().__init__( + routes=routes, + redirect_slashes=redirect_slashes, + default=default, + on_startup=on_startup, + on_shutdown=on_shutdown, + lifespan=lifespan, + ) + if prefix: + assert prefix.startswith("/"), "A path prefix must start with '/'" + assert not prefix.endswith("/"), ( + "A path prefix must not end with '/', as the routes will start with '/'" + ) + self.prefix = prefix + self.tags: List[Union[str, Enum]] = tags or [] + self.dependencies = list(dependencies or []) + self.deprecated = deprecated + self.include_in_schema = include_in_schema + self.responses = responses or {} + self.callbacks = callbacks or [] + self.dependency_overrides_provider = dependency_overrides_provider + self.route_class = route_class + self.default_response_class = default_response_class + self.generate_unique_id_function = generate_unique_id_function + + def route( + self, + path: str, + methods: Optional[List[str]] = None, + name: Optional[str] = None, + include_in_schema: bool = True, + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_route( + path, + func, + methods=methods, + name=name, + include_in_schema=include_in_schema, + ) + return func + + return decorator + + def add_api_route( + self, + path: str, + endpoint: Callable[..., Any], + *, + response_model: Any = Default(None), + status_code: Optional[int] = None, + tags: Optional[List[Union[str, Enum]]] = None, + dependencies: Optional[Sequence[params.Depends]] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + response_description: str = "Successful Response", + responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + deprecated: Optional[bool] = None, + methods: Optional[Union[Set[str], List[str]]] = None, + operation_id: Optional[str] = None, + response_model_include: Optional[IncEx] = None, + response_model_exclude: Optional[IncEx] = None, + response_model_by_alias: bool = True, + response_model_exclude_unset: bool = False, + response_model_exclude_defaults: bool = False, + response_model_exclude_none: bool = False, + include_in_schema: bool = True, + response_class: Union[Type[Response], DefaultPlaceholder] = Default( + JSONResponse + ), + name: Optional[str] = None, + route_class_override: Optional[Type[APIRoute]] = None, + callbacks: Optional[List[BaseRoute]] = None, + openapi_extra: Optional[Dict[str, Any]] = None, + generate_unique_id_function: Union[ + Callable[[APIRoute], str], DefaultPlaceholder + ] = Default(generate_unique_id), + ) -> None: + route_class = route_class_override or self.route_class + responses = responses or {} + combined_responses = {**self.responses, **responses} + current_response_class = get_value_or_default( + response_class, self.default_response_class + ) + current_tags = self.tags.copy() + if tags: + current_tags.extend(tags) + current_dependencies = self.dependencies.copy() + if dependencies: + current_dependencies.extend(dependencies) + current_callbacks = self.callbacks.copy() + if callbacks: + current_callbacks.extend(callbacks) + current_generate_unique_id = get_value_or_default( + generate_unique_id_function, self.generate_unique_id_function + ) + route = route_class( + self.prefix + path, + endpoint=endpoint, + response_model=response_model, + status_code=status_code, + tags=current_tags, + dependencies=current_dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=combined_responses, + deprecated=deprecated or self.deprecated, + methods=methods, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema and self.include_in_schema, + response_class=current_response_class, + name=name, + dependency_overrides_provider=self.dependency_overrides_provider, + callbacks=current_callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=current_generate_unique_id, + ) + self.routes.append(route) + + def api_route( + self, + path: str, + *, + response_model: Any = Default(None), + status_code: Optional[int] = None, + tags: Optional[List[Union[str, Enum]]] = None, + dependencies: Optional[Sequence[params.Depends]] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + response_description: str = "Successful Response", + responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + deprecated: Optional[bool] = None, + methods: Optional[List[str]] = None, + operation_id: Optional[str] = None, + response_model_include: Optional[IncEx] = None, + response_model_exclude: Optional[IncEx] = None, + response_model_by_alias: bool = True, + response_model_exclude_unset: bool = False, + response_model_exclude_defaults: bool = False, + response_model_exclude_none: bool = False, + include_in_schema: bool = True, + response_class: Type[Response] = Default(JSONResponse), + name: Optional[str] = None, + callbacks: Optional[List[BaseRoute]] = None, + openapi_extra: Optional[Dict[str, Any]] = None, + generate_unique_id_function: Callable[[APIRoute], str] = Default( + generate_unique_id + ), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_api_route( + path, + func, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=methods, + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + return func + + return decorator + + def add_api_websocket_route( + self, + path: str, + endpoint: Callable[..., Any], + name: Optional[str] = None, + *, + dependencies: Optional[Sequence[params.Depends]] = None, + ) -> None: + current_dependencies = self.dependencies.copy() + if dependencies: + current_dependencies.extend(dependencies) + + route = APIWebSocketRoute( + self.prefix + path, + endpoint=endpoint, + name=name, + dependencies=current_dependencies, + dependency_overrides_provider=self.dependency_overrides_provider, + ) + self.routes.append(route) + + def websocket( + self, + path: Annotated[ + str, + Doc( + """ + WebSocket path. + """ + ), + ], + name: Annotated[ + Optional[str], + Doc( + """ + A name for the WebSocket. Only used internally. + """ + ), + ] = None, + *, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be used for this + WebSocket. + + Read more about it in the + [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). + """ + ), + ] = None, + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Decorate a WebSocket function. + + Read more about it in the + [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). + + **Example** + + ## Example + + ```python + from fastapi import APIRouter, FastAPI, WebSocket + + app = FastAPI() + router = APIRouter() + + @router.websocket("/ws") + async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text(f"Message text was: {data}") + + app.include_router(router) + ``` + """ + + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_api_websocket_route( + path, func, name=name, dependencies=dependencies + ) + return func + + return decorator + + def websocket_route( + self, path: str, name: Union[str, None] = None + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_websocket_route(path, func, name=name) + return func + + return decorator + + def include_router( + self, + router: Annotated["APIRouter", Doc("The `APIRouter` to include.")], + *, + prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to all the *path operations* in this + router. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to all the + *path operations* in this router. + + Read more about it in the + [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). + """ + ), + ] = None, + default_response_class: Annotated[ + Type[Response], + Doc( + """ + The default response class to be used. + + Read more in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). + """ + ), + ] = Default(JSONResponse), + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses to be shown in OpenAPI. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/). + + And in the + [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + OpenAPI callbacks that should apply to all *path operations* in this + router. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark all *path operations* in this router as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include (or not) all the *path operations* in this router in the + generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = True, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> None: + """ + Include another `APIRouter` in the same current `APIRouter`. + + Read more about it in the + [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/). + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + + app = FastAPI() + internal_router = APIRouter() + users_router = APIRouter() + + @users_router.get("/users/") + def read_users(): + return [{"name": "Rick"}, {"name": "Morty"}] + + internal_router.include_router(users_router) + app.include_router(internal_router) + ``` + """ + if prefix: + assert prefix.startswith("/"), "A path prefix must start with '/'" + assert not prefix.endswith("/"), ( + "A path prefix must not end with '/', as the routes will start with '/'" + ) + else: + for r in router.routes: + path = getattr(r, "path") # noqa: B009 + name = getattr(r, "name", "unknown") + if path is not None and not path: + raise FastAPIError( + f"Prefix and path cannot be both empty (path operation: {name})" + ) + if responses is None: + responses = {} + for route in router.routes: + if isinstance(route, APIRoute): + combined_responses = {**responses, **route.responses} + use_response_class = get_value_or_default( + route.response_class, + router.default_response_class, + default_response_class, + self.default_response_class, + ) + current_tags = [] + if tags: + current_tags.extend(tags) + if route.tags: + current_tags.extend(route.tags) + current_dependencies: List[params.Depends] = [] + if dependencies: + current_dependencies.extend(dependencies) + if route.dependencies: + current_dependencies.extend(route.dependencies) + current_callbacks = [] + if callbacks: + current_callbacks.extend(callbacks) + if route.callbacks: + current_callbacks.extend(route.callbacks) + current_generate_unique_id = get_value_or_default( + route.generate_unique_id_function, + router.generate_unique_id_function, + generate_unique_id_function, + self.generate_unique_id_function, + ) + self.add_api_route( + prefix + route.path, + route.endpoint, + response_model=route.response_model, + status_code=route.status_code, + tags=current_tags, + dependencies=current_dependencies, + summary=route.summary, + description=route.description, + response_description=route.response_description, + responses=combined_responses, + deprecated=route.deprecated or deprecated or self.deprecated, + methods=route.methods, + operation_id=route.operation_id, + response_model_include=route.response_model_include, + response_model_exclude=route.response_model_exclude, + response_model_by_alias=route.response_model_by_alias, + response_model_exclude_unset=route.response_model_exclude_unset, + response_model_exclude_defaults=route.response_model_exclude_defaults, + response_model_exclude_none=route.response_model_exclude_none, + include_in_schema=route.include_in_schema + and self.include_in_schema + and include_in_schema, + response_class=use_response_class, + name=route.name, + route_class_override=type(route), + callbacks=current_callbacks, + openapi_extra=route.openapi_extra, + generate_unique_id_function=current_generate_unique_id, + ) + elif isinstance(route, routing.Route): + methods = list(route.methods or []) + self.add_route( + prefix + route.path, + route.endpoint, + methods=methods, + include_in_schema=route.include_in_schema, + name=route.name, + ) + elif isinstance(route, APIWebSocketRoute): + current_dependencies = [] + if dependencies: + current_dependencies.extend(dependencies) + if route.dependencies: + current_dependencies.extend(route.dependencies) + self.add_api_websocket_route( + prefix + route.path, + route.endpoint, + dependencies=current_dependencies, + name=route.name, + ) + elif isinstance(route, routing.WebSocketRoute): + self.add_websocket_route( + prefix + route.path, route.endpoint, name=route.name + ) + for handler in router.on_startup: + self.add_event_handler("startup", handler) + for handler in router.on_shutdown: + self.add_event_handler("shutdown", handler) + self.lifespan_context = _merge_lifespan_context( + self.lifespan_context, + router.lifespan_context, + ) + + def get( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP GET operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + + app = FastAPI() + router = APIRouter() + + @router.get("/items/") + def read_items(): + return [{"name": "Empanada"}, {"name": "Arepa"}] + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["GET"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def put( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP PUT operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + router = APIRouter() + + @router.put("/items/{item_id}") + def replace_item(item_id: str, item: Item): + return {"message": "Item replaced", "id": item_id} + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["PUT"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def post( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP POST operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + router = APIRouter() + + @router.post("/items/") + def create_item(item: Item): + return {"message": "Item created"} + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["POST"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def delete( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP DELETE operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + + app = FastAPI() + router = APIRouter() + + @router.delete("/items/{item_id}") + def delete_item(item_id: str): + return {"message": "Item deleted"} + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["DELETE"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def options( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP OPTIONS operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + + app = FastAPI() + router = APIRouter() + + @router.options("/items/") + def get_item_options(): + return {"additions": ["Aji", "Guacamole"]} + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["OPTIONS"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def head( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP HEAD operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + router = APIRouter() + + @router.head("/items/", status_code=204) + def get_items_headers(response: Response): + response.headers["X-Cat-Dog"] = "Alone in the world" + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["HEAD"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def patch( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP PATCH operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + router = APIRouter() + + @router.patch("/items/") + def update_item(item: Item): + return {"message": "Item updated in place"} + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["PATCH"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + def trace( + self, + path: Annotated[ + str, + Doc( + """ + The URL path to be used for this *path operation*. + + For example, in `http://example.com/items`, the path is `/items`. + """ + ), + ], + *, + response_model: Annotated[ + Any, + Doc( + """ + The type to use for the response. + + It could be any valid Pydantic *field* type. So, it doesn't have to + be a Pydantic model, it could be other things, like a `list`, `dict`, + etc. + + It will be used for: + + * Documentation: the generated OpenAPI (and the UI at `/docs`) will + show it as the response (JSON Schema). + * Serialization: you could return an arbitrary object and the + `response_model` would be used to serialize that object into the + corresponding JSON. + * Filtering: the JSON sent to the client will only contain the data + (fields) defined in the `response_model`. If you returned an object + that contains an attribute `password` but the `response_model` does + not include that field, the JSON sent to the client would not have + that `password`. + * Validation: whatever you return will be serialized with the + `response_model`, converting any data as necessary to generate the + corresponding JSON. But if the data in the object returned is not + valid, that would mean a violation of the contract with the client, + so it's an error from the API developer. So, FastAPI will raise an + error and return a 500 error code (Internal Server Error). + + Read more about it in the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + """ + ), + ] = Default(None), + status_code: Annotated[ + Optional[int], + Doc( + """ + The default status code to be used for the response. + + You could override the status code by returning a response directly. + + Read more about it in the + [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). + """ + ), + ] = None, + tags: Annotated[ + Optional[List[Union[str, Enum]]], + Doc( + """ + A list of tags to be applied to the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). + """ + ), + ] = None, + dependencies: Annotated[ + Optional[Sequence[params.Depends]], + Doc( + """ + A list of dependencies (using `Depends()`) to be applied to the + *path operation*. + + Read more about it in the + [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). + """ + ), + ] = None, + summary: Annotated[ + Optional[str], + Doc( + """ + A summary for the *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + A description for the *path operation*. + + If not provided, it will be extracted automatically from the docstring + of the *path operation function*. + + It can contain Markdown. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). + """ + ), + ] = None, + response_description: Annotated[ + str, + Doc( + """ + The description for the default response. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = "Successful Response", + responses: Annotated[ + Optional[Dict[Union[int, str], Dict[str, Any]]], + Doc( + """ + Additional responses that could be returned by this *path operation*. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + deprecated: Annotated[ + Optional[bool], + Doc( + """ + Mark this *path operation* as deprecated. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + operation_id: Annotated[ + Optional[str], + Doc( + """ + Custom operation ID to be used by this *path operation*. + + By default, it is generated automatically. + + If you provide a custom operation ID, you need to make sure it is + unique for the whole API. + + You can customize the + operation ID generation with the parameter + `generate_unique_id_function` in the `FastAPI` class. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = None, + response_model_include: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to include only certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_exclude: Annotated[ + Optional[IncEx], + Doc( + """ + Configuration passed to Pydantic to exclude certain fields in the + response data. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = None, + response_model_by_alias: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response model + should be serialized by alias when an alias is used. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). + """ + ), + ] = True, + response_model_exclude_unset: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that were not set and + have their default values. This is different from + `response_model_exclude_defaults` in that if the fields are set, + they will be included in the response, even if the value is the same + as the default. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_defaults: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data + should have all the fields, including the ones that have the same value + as the default. This is different from `response_model_exclude_unset` + in that if the fields are set but contain the same default values, + they will be excluded from the response. + + When `True`, default values are omitted from the response. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). + """ + ), + ] = False, + response_model_exclude_none: Annotated[ + bool, + Doc( + """ + Configuration passed to Pydantic to define if the response data should + exclude fields set to `None`. + + This is much simpler (less smart) than `response_model_exclude_unset` + and `response_model_exclude_defaults`. You probably want to use one of + those two instead of this one, as those allow returning `None` values + when it makes sense. + + Read more about it in the + [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). + """ + ), + ] = False, + include_in_schema: Annotated[ + bool, + Doc( + """ + Include this *path operation* in the generated OpenAPI schema. + + This affects the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-from-openapi). + """ + ), + ] = True, + response_class: Annotated[ + Type[Response], + Doc( + """ + Response class to be used for this *path operation*. + + This will not be used if you return a response directly. + + Read more about it in the + [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). + """ + ), + ] = Default(JSONResponse), + name: Annotated[ + Optional[str], + Doc( + """ + Name for this *path operation*. Only used internally. + """ + ), + ] = None, + callbacks: Annotated[ + Optional[List[BaseRoute]], + Doc( + """ + List of *path operations* that will be used as OpenAPI callbacks. + + This is only for OpenAPI documentation, the callbacks won't be used + directly. + + It will be added to the generated OpenAPI (e.g. visible at `/docs`). + + Read more about it in the + [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). + """ + ), + ] = None, + openapi_extra: Annotated[ + Optional[Dict[str, Any]], + Doc( + """ + Extra metadata to be included in the OpenAPI schema for this *path + operation*. + + Read more about it in the + [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). + """ + ), + ] = None, + generate_unique_id_function: Annotated[ + Callable[[APIRoute], str], + Doc( + """ + Customize the function used to generate unique IDs for the *path + operations* shown in the generated OpenAPI. + + This is particularly useful when automatically generating clients or + SDKs for your API. + + Read more about it in the + [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). + """ + ), + ] = Default(generate_unique_id), + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add a *path operation* using an HTTP TRACE operation. + + ## Example + + ```python + from fastapi import APIRouter, FastAPI + from pydantic import BaseModel + + class Item(BaseModel): + name: str + description: str | None = None + + app = FastAPI() + router = APIRouter() + + @router.trace("/items/{item_id}") + def trace_item(item_id: str): + return None + + app.include_router(router) + ``` + """ + return self.api_route( + path=path, + response_model=response_model, + status_code=status_code, + tags=tags, + dependencies=dependencies, + summary=summary, + description=description, + response_description=response_description, + responses=responses, + deprecated=deprecated, + methods=["TRACE"], + operation_id=operation_id, + response_model_include=response_model_include, + response_model_exclude=response_model_exclude, + response_model_by_alias=response_model_by_alias, + response_model_exclude_unset=response_model_exclude_unset, + response_model_exclude_defaults=response_model_exclude_defaults, + response_model_exclude_none=response_model_exclude_none, + include_in_schema=include_in_schema, + response_class=response_class, + name=name, + callbacks=callbacks, + openapi_extra=openapi_extra, + generate_unique_id_function=generate_unique_id_function, + ) + + @deprecated( + """ + on_event is deprecated, use lifespan event handlers instead. + + Read more about it in the + [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/). + """ + ) + def on_event( + self, + event_type: Annotated[ + str, + Doc( + """ + The type of event. `startup` or `shutdown`. + """ + ), + ], + ) -> Callable[[DecoratedCallable], DecoratedCallable]: + """ + Add an event handler for the router. + + `on_event` is deprecated, use `lifespan` event handlers instead. + + Read more about it in the + [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated). + """ + + def decorator(func: DecoratedCallable) -> DecoratedCallable: + self.add_event_handler(event_type, func) + return func + + return decorator diff --git a/.venv/Lib/site-packages/fastapi/security/__init__.py b/.venv/Lib/site-packages/fastapi/security/__init__.py new file mode 100644 index 0000000..3aa6bf2 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/security/__init__.py @@ -0,0 +1,15 @@ +from .api_key import APIKeyCookie as APIKeyCookie +from .api_key import APIKeyHeader as APIKeyHeader +from .api_key import APIKeyQuery as APIKeyQuery +from .http import HTTPAuthorizationCredentials as HTTPAuthorizationCredentials +from .http import HTTPBasic as HTTPBasic +from .http import HTTPBasicCredentials as HTTPBasicCredentials +from .http import HTTPBearer as HTTPBearer +from .http import HTTPDigest as HTTPDigest +from .oauth2 import OAuth2 as OAuth2 +from .oauth2 import OAuth2AuthorizationCodeBearer as OAuth2AuthorizationCodeBearer +from .oauth2 import OAuth2PasswordBearer as OAuth2PasswordBearer +from .oauth2 import OAuth2PasswordRequestForm as OAuth2PasswordRequestForm +from .oauth2 import OAuth2PasswordRequestFormStrict as OAuth2PasswordRequestFormStrict +from .oauth2 import SecurityScopes as SecurityScopes +from .open_id_connect_url import OpenIdConnect as OpenIdConnect diff --git a/.venv/Lib/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..413d99e3f2c1cd76f92800b777f1e1277bacddf2 GIT binary patch literal 869 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b8013161d6d2b8e0600c090396c20b5d6e7e0bd GIT binary patch literal 13658 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc b/.venv/Lib/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11af82a159b6e45deb0982fdebcfa679d4cfeed6 GIT binary patch literal 624 zcmY*VL2DC16rS1HU1Bv>u@dV+7C~_lTSNs(RV*z*L24m-2?NWx`;u(3n_XvS1CazO zc*wE0p1mph1N;wOEFKJVQSjs~5>K9-iBa^yJihOH-#k9v`*L{+>3SPh+ZQlGUk&+F z?gGrNYjA)f6yXG=xPeovVPOp&<4tuY}tckg?B;nfl*y7V3kyTHBe ztdr5TKJ@zZLHvY@Si+TDk5~%?ZR$@fkJkl+1&`%my^wJt*7AWm*8&;*f2`m6f_XN+ zth1SX;<9dg=(~%KU7ReRo>Z=Ws%#uqHr{Q0tlT}cSN3Y-&2cd~dUF59`1S|;%uL(S zj_)W}4)VCy;^w;CY*Iu>KHvKq@e@WpZq(APG>eJ^?(h{&&4q=p+mv98Kcn+g*Tt(7 d@6xMNFUwP+@fT-ld~KhMZ$1xxIGWE_{Q_*yq_hA4 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/fastapi/security/api_key.py b/.venv/Lib/site-packages/fastapi/security/api_key.py new file mode 100644 index 0000000..70c2dca --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/security/api_key.py @@ -0,0 +1,288 @@ +from typing import Optional + +from fastapi.openapi.models import APIKey, APIKeyIn +from fastapi.security.base import SecurityBase +from starlette.exceptions import HTTPException +from starlette.requests import Request +from starlette.status import HTTP_403_FORBIDDEN +from typing_extensions import Annotated, Doc + + +class APIKeyBase(SecurityBase): + @staticmethod + def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]: + if not api_key: + if auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + return None + return api_key + + +class APIKeyQuery(APIKeyBase): + """ + API key authentication using a query parameter. + + This defines the name of the query parameter that should be provided in the request + with the API key and integrates that into the OpenAPI documentation. It extracts + the key value sent in the query parameter automatically and provides it as the + dependency result. But it doesn't define how to send that API key to the client. + + ## Usage + + Create an instance object and use that object as the dependency in `Depends()`. + + The dependency result will be a string containing the key value. + + ## Example + + ```python + from fastapi import Depends, FastAPI + from fastapi.security import APIKeyQuery + + app = FastAPI() + + query_scheme = APIKeyQuery(name="api_key") + + + @app.get("/items/") + async def read_items(api_key: str = Depends(query_scheme)): + return {"api_key": api_key} + ``` + """ + + def __init__( + self, + *, + name: Annotated[ + str, + Doc("Query parameter name."), + ], + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if the query parameter is not provided, `APIKeyQuery` will + automatically cancel the request and send the client an error. + + If `auto_error` is set to `False`, when the query parameter is not + available, instead of erroring out, the dependency result will be + `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, in a query + parameter or in an HTTP Bearer token). + """ + ), + ] = True, + ): + self.model: APIKey = APIKey( + **{"in": APIKeyIn.query}, # type: ignore[arg-type] + name=name, + description=description, + ) + self.scheme_name = scheme_name or self.__class__.__name__ + self.auto_error = auto_error + + async def __call__(self, request: Request) -> Optional[str]: + api_key = request.query_params.get(self.model.name) + return self.check_api_key(api_key, self.auto_error) + + +class APIKeyHeader(APIKeyBase): + """ + API key authentication using a header. + + This defines the name of the header that should be provided in the request with + the API key and integrates that into the OpenAPI documentation. It extracts + the key value sent in the header automatically and provides it as the dependency + result. But it doesn't define how to send that key to the client. + + ## Usage + + Create an instance object and use that object as the dependency in `Depends()`. + + The dependency result will be a string containing the key value. + + ## Example + + ```python + from fastapi import Depends, FastAPI + from fastapi.security import APIKeyHeader + + app = FastAPI() + + header_scheme = APIKeyHeader(name="x-key") + + + @app.get("/items/") + async def read_items(key: str = Depends(header_scheme)): + return {"key": key} + ``` + """ + + def __init__( + self, + *, + name: Annotated[str, Doc("Header name.")], + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if the header is not provided, `APIKeyHeader` will + automatically cancel the request and send the client an error. + + If `auto_error` is set to `False`, when the header is not available, + instead of erroring out, the dependency result will be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, in a header or + in an HTTP Bearer token). + """ + ), + ] = True, + ): + self.model: APIKey = APIKey( + **{"in": APIKeyIn.header}, # type: ignore[arg-type] + name=name, + description=description, + ) + self.scheme_name = scheme_name or self.__class__.__name__ + self.auto_error = auto_error + + async def __call__(self, request: Request) -> Optional[str]: + api_key = request.headers.get(self.model.name) + return self.check_api_key(api_key, self.auto_error) + + +class APIKeyCookie(APIKeyBase): + """ + API key authentication using a cookie. + + This defines the name of the cookie that should be provided in the request with + the API key and integrates that into the OpenAPI documentation. It extracts + the key value sent in the cookie automatically and provides it as the dependency + result. But it doesn't define how to set that cookie. + + ## Usage + + Create an instance object and use that object as the dependency in `Depends()`. + + The dependency result will be a string containing the key value. + + ## Example + + ```python + from fastapi import Depends, FastAPI + from fastapi.security import APIKeyCookie + + app = FastAPI() + + cookie_scheme = APIKeyCookie(name="session") + + + @app.get("/items/") + async def read_items(session: str = Depends(cookie_scheme)): + return {"session": session} + ``` + """ + + def __init__( + self, + *, + name: Annotated[str, Doc("Cookie name.")], + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if the cookie is not provided, `APIKeyCookie` will + automatically cancel the request and send the client an error. + + If `auto_error` is set to `False`, when the cookie is not available, + instead of erroring out, the dependency result will be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, in a cookie or + in an HTTP Bearer token). + """ + ), + ] = True, + ): + self.model: APIKey = APIKey( + **{"in": APIKeyIn.cookie}, # type: ignore[arg-type] + name=name, + description=description, + ) + self.scheme_name = scheme_name or self.__class__.__name__ + self.auto_error = auto_error + + async def __call__(self, request: Request) -> Optional[str]: + api_key = request.cookies.get(self.model.name) + return self.check_api_key(api_key, self.auto_error) diff --git a/.venv/Lib/site-packages/fastapi/security/base.py b/.venv/Lib/site-packages/fastapi/security/base.py new file mode 100644 index 0000000..c43555d --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/security/base.py @@ -0,0 +1,6 @@ +from fastapi.openapi.models import SecurityBase as SecurityBaseModel + + +class SecurityBase: + model: SecurityBaseModel + scheme_name: str diff --git a/.venv/Lib/site-packages/fastapi/security/http.py b/.venv/Lib/site-packages/fastapi/security/http.py new file mode 100644 index 0000000..9ab2df3 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/security/http.py @@ -0,0 +1,423 @@ +import binascii +from base64 import b64decode +from typing import Optional + +from fastapi.exceptions import HTTPException +from fastapi.openapi.models import HTTPBase as HTTPBaseModel +from fastapi.openapi.models import HTTPBearer as HTTPBearerModel +from fastapi.security.base import SecurityBase +from fastapi.security.utils import get_authorization_scheme_param +from pydantic import BaseModel +from starlette.requests import Request +from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN +from typing_extensions import Annotated, Doc + + +class HTTPBasicCredentials(BaseModel): + """ + The HTTP Basic credentials given as the result of using `HTTPBasic` in a + dependency. + + Read more about it in the + [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/). + """ + + username: Annotated[str, Doc("The HTTP Basic username.")] + password: Annotated[str, Doc("The HTTP Basic password.")] + + +class HTTPAuthorizationCredentials(BaseModel): + """ + The HTTP authorization credentials in the result of using `HTTPBearer` or + `HTTPDigest` in a dependency. + + The HTTP authorization header value is split by the first space. + + The first part is the `scheme`, the second part is the `credentials`. + + For example, in an HTTP Bearer token scheme, the client will send a header + like: + + ``` + Authorization: Bearer deadbeef12346 + ``` + + In this case: + + * `scheme` will have the value `"Bearer"` + * `credentials` will have the value `"deadbeef12346"` + """ + + scheme: Annotated[ + str, + Doc( + """ + The HTTP authorization scheme extracted from the header value. + """ + ), + ] + credentials: Annotated[ + str, + Doc( + """ + The HTTP authorization credentials extracted from the header value. + """ + ), + ] + + +class HTTPBase(SecurityBase): + def __init__( + self, + *, + scheme: str, + scheme_name: Optional[str] = None, + description: Optional[str] = None, + auto_error: bool = True, + ): + self.model = HTTPBaseModel(scheme=scheme, description=description) + self.scheme_name = scheme_name or self.__class__.__name__ + self.auto_error = auto_error + + async def __call__( + self, request: Request + ) -> Optional[HTTPAuthorizationCredentials]: + authorization = request.headers.get("Authorization") + scheme, credentials = get_authorization_scheme_param(authorization) + if not (authorization and scheme and credentials): + if self.auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + else: + return None + return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) + + +class HTTPBasic(HTTPBase): + """ + HTTP Basic authentication. + + ## Usage + + Create an instance object and use that object as the dependency in `Depends()`. + + The dependency result will be an `HTTPBasicCredentials` object containing the + `username` and the `password`. + + Read more about it in the + [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/). + + ## Example + + ```python + from typing import Annotated + + from fastapi import Depends, FastAPI + from fastapi.security import HTTPBasic, HTTPBasicCredentials + + app = FastAPI() + + security = HTTPBasic() + + + @app.get("/users/me") + def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]): + return {"username": credentials.username, "password": credentials.password} + ``` + """ + + def __init__( + self, + *, + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + realm: Annotated[ + Optional[str], + Doc( + """ + HTTP Basic authentication realm. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if the HTTP Basic authentication is not provided (a + header), `HTTPBasic` will automatically cancel the request and send the + client an error. + + If `auto_error` is set to `False`, when the HTTP Basic authentication + is not available, instead of erroring out, the dependency result will + be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, in HTTP Basic + authentication or in an HTTP Bearer token). + """ + ), + ] = True, + ): + self.model = HTTPBaseModel(scheme="basic", description=description) + self.scheme_name = scheme_name or self.__class__.__name__ + self.realm = realm + self.auto_error = auto_error + + async def __call__( # type: ignore + self, request: Request + ) -> Optional[HTTPBasicCredentials]: + authorization = request.headers.get("Authorization") + scheme, param = get_authorization_scheme_param(authorization) + if self.realm: + unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{self.realm}"'} + else: + unauthorized_headers = {"WWW-Authenticate": "Basic"} + if not authorization or scheme.lower() != "basic": + if self.auto_error: + raise HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers=unauthorized_headers, + ) + else: + return None + invalid_user_credentials_exc = HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail="Invalid authentication credentials", + headers=unauthorized_headers, + ) + try: + data = b64decode(param).decode("ascii") + except (ValueError, UnicodeDecodeError, binascii.Error): + raise invalid_user_credentials_exc # noqa: B904 + username, separator, password = data.partition(":") + if not separator: + raise invalid_user_credentials_exc + return HTTPBasicCredentials(username=username, password=password) + + +class HTTPBearer(HTTPBase): + """ + HTTP Bearer token authentication. + + ## Usage + + Create an instance object and use that object as the dependency in `Depends()`. + + The dependency result will be an `HTTPAuthorizationCredentials` object containing + the `scheme` and the `credentials`. + + ## Example + + ```python + from typing import Annotated + + from fastapi import Depends, FastAPI + from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer + + app = FastAPI() + + security = HTTPBearer() + + + @app.get("/users/me") + def read_current_user( + credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)] + ): + return {"scheme": credentials.scheme, "credentials": credentials.credentials} + ``` + """ + + def __init__( + self, + *, + bearerFormat: Annotated[Optional[str], Doc("Bearer token format.")] = None, + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if the HTTP Bearer token is not provided (in an + `Authorization` header), `HTTPBearer` will automatically cancel the + request and send the client an error. + + If `auto_error` is set to `False`, when the HTTP Bearer token + is not available, instead of erroring out, the dependency result will + be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, in an HTTP + Bearer token or in a cookie). + """ + ), + ] = True, + ): + self.model = HTTPBearerModel(bearerFormat=bearerFormat, description=description) + self.scheme_name = scheme_name or self.__class__.__name__ + self.auto_error = auto_error + + async def __call__( + self, request: Request + ) -> Optional[HTTPAuthorizationCredentials]: + authorization = request.headers.get("Authorization") + scheme, credentials = get_authorization_scheme_param(authorization) + if not (authorization and scheme and credentials): + if self.auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + else: + return None + if scheme.lower() != "bearer": + if self.auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, + detail="Invalid authentication credentials", + ) + else: + return None + return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) + + +class HTTPDigest(HTTPBase): + """ + HTTP Digest authentication. + + ## Usage + + Create an instance object and use that object as the dependency in `Depends()`. + + The dependency result will be an `HTTPAuthorizationCredentials` object containing + the `scheme` and the `credentials`. + + ## Example + + ```python + from typing import Annotated + + from fastapi import Depends, FastAPI + from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest + + app = FastAPI() + + security = HTTPDigest() + + + @app.get("/users/me") + def read_current_user( + credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)] + ): + return {"scheme": credentials.scheme, "credentials": credentials.credentials} + ``` + """ + + def __init__( + self, + *, + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if the HTTP Digest is not provided, `HTTPDigest` will + automatically cancel the request and send the client an error. + + If `auto_error` is set to `False`, when the HTTP Digest is not + available, instead of erroring out, the dependency result will + be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, in HTTP + Digest or in a cookie). + """ + ), + ] = True, + ): + self.model = HTTPBaseModel(scheme="digest", description=description) + self.scheme_name = scheme_name or self.__class__.__name__ + self.auto_error = auto_error + + async def __call__( + self, request: Request + ) -> Optional[HTTPAuthorizationCredentials]: + authorization = request.headers.get("Authorization") + scheme, credentials = get_authorization_scheme_param(authorization) + if not (authorization and scheme and credentials): + if self.auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + else: + return None + if scheme.lower() != "digest": + if self.auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, + detail="Invalid authentication credentials", + ) + else: + return None + return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) diff --git a/.venv/Lib/site-packages/fastapi/security/oauth2.py b/.venv/Lib/site-packages/fastapi/security/oauth2.py new file mode 100644 index 0000000..5ffad59 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/security/oauth2.py @@ -0,0 +1,638 @@ +from typing import Any, Dict, List, Optional, Union, cast + +from fastapi.exceptions import HTTPException +from fastapi.openapi.models import OAuth2 as OAuth2Model +from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel +from fastapi.param_functions import Form +from fastapi.security.base import SecurityBase +from fastapi.security.utils import get_authorization_scheme_param +from starlette.requests import Request +from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN + +# TODO: import from typing when deprecating Python 3.9 +from typing_extensions import Annotated, Doc + + +class OAuth2PasswordRequestForm: + """ + This is a dependency class to collect the `username` and `password` as form data + for an OAuth2 password flow. + + The OAuth2 specification dictates that for a password flow the data should be + collected using form data (instead of JSON) and that it should have the specific + fields `username` and `password`. + + All the initialization parameters are extracted from the request. + + Read more about it in the + [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). + + ## Example + + ```python + from typing import Annotated + + from fastapi import Depends, FastAPI + from fastapi.security import OAuth2PasswordRequestForm + + app = FastAPI() + + + @app.post("/login") + def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]): + data = {} + data["scopes"] = [] + for scope in form_data.scopes: + data["scopes"].append(scope) + if form_data.client_id: + data["client_id"] = form_data.client_id + if form_data.client_secret: + data["client_secret"] = form_data.client_secret + return data + ``` + + Note that for OAuth2 the scope `items:read` is a single scope in an opaque string. + You could have custom internal logic to separate it by colon characters (`:`) or + similar, and get the two parts `items` and `read`. Many applications do that to + group and organize permissions, you could do it as well in your application, just + know that that it is application specific, it's not part of the specification. + """ + + def __init__( + self, + *, + grant_type: Annotated[ + Union[str, None], + Form(pattern="^password$"), + Doc( + """ + The OAuth2 spec says it is required and MUST be the fixed string + "password". Nevertheless, this dependency class is permissive and + allows not passing it. If you want to enforce it, use instead the + `OAuth2PasswordRequestFormStrict` dependency. + """ + ), + ] = None, + username: Annotated[ + str, + Form(), + Doc( + """ + `username` string. The OAuth2 spec requires the exact field name + `username`. + """ + ), + ], + password: Annotated[ + str, + Form(), + Doc( + """ + `password` string. The OAuth2 spec requires the exact field name + `password". + """ + ), + ], + scope: Annotated[ + str, + Form(), + Doc( + """ + A single string with actually several scopes separated by spaces. Each + scope is also a string. + + For example, a single string with: + + ```python + "items:read items:write users:read profile openid" + ```` + + would represent the scopes: + + * `items:read` + * `items:write` + * `users:read` + * `profile` + * `openid` + """ + ), + ] = "", + client_id: Annotated[ + Union[str, None], + Form(), + Doc( + """ + If there's a `client_id`, it can be sent as part of the form fields. + But the OAuth2 specification recommends sending the `client_id` and + `client_secret` (if any) using HTTP Basic auth. + """ + ), + ] = None, + client_secret: Annotated[ + Union[str, None], + Form(), + Doc( + """ + If there's a `client_password` (and a `client_id`), they can be sent + as part of the form fields. But the OAuth2 specification recommends + sending the `client_id` and `client_secret` (if any) using HTTP Basic + auth. + """ + ), + ] = None, + ): + self.grant_type = grant_type + self.username = username + self.password = password + self.scopes = scope.split() + self.client_id = client_id + self.client_secret = client_secret + + +class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): + """ + This is a dependency class to collect the `username` and `password` as form data + for an OAuth2 password flow. + + The OAuth2 specification dictates that for a password flow the data should be + collected using form data (instead of JSON) and that it should have the specific + fields `username` and `password`. + + All the initialization parameters are extracted from the request. + + The only difference between `OAuth2PasswordRequestFormStrict` and + `OAuth2PasswordRequestForm` is that `OAuth2PasswordRequestFormStrict` requires the + client to send the form field `grant_type` with the value `"password"`, which + is required in the OAuth2 specification (it seems that for no particular reason), + while for `OAuth2PasswordRequestForm` `grant_type` is optional. + + Read more about it in the + [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). + + ## Example + + ```python + from typing import Annotated + + from fastapi import Depends, FastAPI + from fastapi.security import OAuth2PasswordRequestForm + + app = FastAPI() + + + @app.post("/login") + def login(form_data: Annotated[OAuth2PasswordRequestFormStrict, Depends()]): + data = {} + data["scopes"] = [] + for scope in form_data.scopes: + data["scopes"].append(scope) + if form_data.client_id: + data["client_id"] = form_data.client_id + if form_data.client_secret: + data["client_secret"] = form_data.client_secret + return data + ``` + + Note that for OAuth2 the scope `items:read` is a single scope in an opaque string. + You could have custom internal logic to separate it by colon characters (`:`) or + similar, and get the two parts `items` and `read`. Many applications do that to + group and organize permissions, you could do it as well in your application, just + know that that it is application specific, it's not part of the specification. + + + grant_type: the OAuth2 spec says it is required and MUST be the fixed string "password". + This dependency is strict about it. If you want to be permissive, use instead the + OAuth2PasswordRequestForm dependency class. + username: username string. The OAuth2 spec requires the exact field name "username". + password: password string. The OAuth2 spec requires the exact field name "password". + scope: Optional string. Several scopes (each one a string) separated by spaces. E.g. + "items:read items:write users:read profile openid" + client_id: optional string. OAuth2 recommends sending the client_id and client_secret (if any) + using HTTP Basic auth, as: client_id:client_secret + client_secret: optional string. OAuth2 recommends sending the client_id and client_secret (if any) + using HTTP Basic auth, as: client_id:client_secret + """ + + def __init__( + self, + grant_type: Annotated[ + str, + Form(pattern="^password$"), + Doc( + """ + The OAuth2 spec says it is required and MUST be the fixed string + "password". This dependency is strict about it. If you want to be + permissive, use instead the `OAuth2PasswordRequestForm` dependency + class. + """ + ), + ], + username: Annotated[ + str, + Form(), + Doc( + """ + `username` string. The OAuth2 spec requires the exact field name + `username`. + """ + ), + ], + password: Annotated[ + str, + Form(), + Doc( + """ + `password` string. The OAuth2 spec requires the exact field name + `password". + """ + ), + ], + scope: Annotated[ + str, + Form(), + Doc( + """ + A single string with actually several scopes separated by spaces. Each + scope is also a string. + + For example, a single string with: + + ```python + "items:read items:write users:read profile openid" + ```` + + would represent the scopes: + + * `items:read` + * `items:write` + * `users:read` + * `profile` + * `openid` + """ + ), + ] = "", + client_id: Annotated[ + Union[str, None], + Form(), + Doc( + """ + If there's a `client_id`, it can be sent as part of the form fields. + But the OAuth2 specification recommends sending the `client_id` and + `client_secret` (if any) using HTTP Basic auth. + """ + ), + ] = None, + client_secret: Annotated[ + Union[str, None], + Form(), + Doc( + """ + If there's a `client_password` (and a `client_id`), they can be sent + as part of the form fields. But the OAuth2 specification recommends + sending the `client_id` and `client_secret` (if any) using HTTP Basic + auth. + """ + ), + ] = None, + ): + super().__init__( + grant_type=grant_type, + username=username, + password=password, + scope=scope, + client_id=client_id, + client_secret=client_secret, + ) + + +class OAuth2(SecurityBase): + """ + This is the base class for OAuth2 authentication, an instance of it would be used + as a dependency. All other OAuth2 classes inherit from it and customize it for + each OAuth2 flow. + + You normally would not create a new class inheriting from it but use one of the + existing subclasses, and maybe compose them if you want to support multiple flows. + + Read more about it in the + [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/). + """ + + def __init__( + self, + *, + flows: Annotated[ + Union[OAuthFlowsModel, Dict[str, Dict[str, Any]]], + Doc( + """ + The dictionary of OAuth2 flows. + """ + ), + ] = OAuthFlowsModel(), + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if no HTTP Authorization header is provided, required for + OAuth2 authentication, it will automatically cancel the request and + send the client an error. + + If `auto_error` is set to `False`, when the HTTP Authorization header + is not available, instead of erroring out, the dependency result will + be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, with OAuth2 + or in a cookie). + """ + ), + ] = True, + ): + self.model = OAuth2Model( + flows=cast(OAuthFlowsModel, flows), description=description + ) + self.scheme_name = scheme_name or self.__class__.__name__ + self.auto_error = auto_error + + async def __call__(self, request: Request) -> Optional[str]: + authorization = request.headers.get("Authorization") + if not authorization: + if self.auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + else: + return None + return authorization + + +class OAuth2PasswordBearer(OAuth2): + """ + OAuth2 flow for authentication using a bearer token obtained with a password. + An instance of it would be used as a dependency. + + Read more about it in the + [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). + """ + + def __init__( + self, + tokenUrl: Annotated[ + str, + Doc( + """ + The URL to obtain the OAuth2 token. This would be the *path operation* + that has `OAuth2PasswordRequestForm` as a dependency. + """ + ), + ], + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + scopes: Annotated[ + Optional[Dict[str, str]], + Doc( + """ + The OAuth2 scopes that would be required by the *path operations* that + use this dependency. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if no HTTP Authorization header is provided, required for + OAuth2 authentication, it will automatically cancel the request and + send the client an error. + + If `auto_error` is set to `False`, when the HTTP Authorization header + is not available, instead of erroring out, the dependency result will + be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, with OAuth2 + or in a cookie). + """ + ), + ] = True, + ): + if not scopes: + scopes = {} + flows = OAuthFlowsModel( + password=cast(Any, {"tokenUrl": tokenUrl, "scopes": scopes}) + ) + super().__init__( + flows=flows, + scheme_name=scheme_name, + description=description, + auto_error=auto_error, + ) + + async def __call__(self, request: Request) -> Optional[str]: + authorization = request.headers.get("Authorization") + scheme, param = get_authorization_scheme_param(authorization) + if not authorization or scheme.lower() != "bearer": + if self.auto_error: + raise HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}, + ) + else: + return None + return param + + +class OAuth2AuthorizationCodeBearer(OAuth2): + """ + OAuth2 flow for authentication using a bearer token obtained with an OAuth2 code + flow. An instance of it would be used as a dependency. + """ + + def __init__( + self, + authorizationUrl: str, + tokenUrl: Annotated[ + str, + Doc( + """ + The URL to obtain the OAuth2 token. + """ + ), + ], + refreshUrl: Annotated[ + Optional[str], + Doc( + """ + The URL to refresh the token and obtain a new one. + """ + ), + ] = None, + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + scopes: Annotated[ + Optional[Dict[str, str]], + Doc( + """ + The OAuth2 scopes that would be required by the *path operations* that + use this dependency. + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if no HTTP Authorization header is provided, required for + OAuth2 authentication, it will automatically cancel the request and + send the client an error. + + If `auto_error` is set to `False`, when the HTTP Authorization header + is not available, instead of erroring out, the dependency result will + be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, with OAuth2 + or in a cookie). + """ + ), + ] = True, + ): + if not scopes: + scopes = {} + flows = OAuthFlowsModel( + authorizationCode=cast( + Any, + { + "authorizationUrl": authorizationUrl, + "tokenUrl": tokenUrl, + "refreshUrl": refreshUrl, + "scopes": scopes, + }, + ) + ) + super().__init__( + flows=flows, + scheme_name=scheme_name, + description=description, + auto_error=auto_error, + ) + + async def __call__(self, request: Request) -> Optional[str]: + authorization = request.headers.get("Authorization") + scheme, param = get_authorization_scheme_param(authorization) + if not authorization or scheme.lower() != "bearer": + if self.auto_error: + raise HTTPException( + status_code=HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}, + ) + else: + return None # pragma: nocover + return param + + +class SecurityScopes: + """ + This is a special class that you can define in a parameter in a dependency to + obtain the OAuth2 scopes required by all the dependencies in the same chain. + + This way, multiple dependencies can have different scopes, even when used in the + same *path operation*. And with this, you can access all the scopes required in + all those dependencies in a single place. + + Read more about it in the + [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/). + """ + + def __init__( + self, + scopes: Annotated[ + Optional[List[str]], + Doc( + """ + This will be filled by FastAPI. + """ + ), + ] = None, + ): + self.scopes: Annotated[ + List[str], + Doc( + """ + The list of all the scopes required by dependencies. + """ + ), + ] = scopes or [] + self.scope_str: Annotated[ + str, + Doc( + """ + All the scopes required by all the dependencies in a single string + separated by spaces, as defined in the OAuth2 specification. + """ + ), + ] = " ".join(self.scopes) diff --git a/.venv/Lib/site-packages/fastapi/security/open_id_connect_url.py b/.venv/Lib/site-packages/fastapi/security/open_id_connect_url.py new file mode 100644 index 0000000..c8cceb9 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/security/open_id_connect_url.py @@ -0,0 +1,84 @@ +from typing import Optional + +from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel +from fastapi.security.base import SecurityBase +from starlette.exceptions import HTTPException +from starlette.requests import Request +from starlette.status import HTTP_403_FORBIDDEN +from typing_extensions import Annotated, Doc + + +class OpenIdConnect(SecurityBase): + """ + OpenID Connect authentication class. An instance of it would be used as a + dependency. + """ + + def __init__( + self, + *, + openIdConnectUrl: Annotated[ + str, + Doc( + """ + The OpenID Connect URL. + """ + ), + ], + scheme_name: Annotated[ + Optional[str], + Doc( + """ + Security scheme name. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + description: Annotated[ + Optional[str], + Doc( + """ + Security scheme description. + + It will be included in the generated OpenAPI (e.g. visible at `/docs`). + """ + ), + ] = None, + auto_error: Annotated[ + bool, + Doc( + """ + By default, if no HTTP Authorization header is provided, required for + OpenID Connect authentication, it will automatically cancel the request + and send the client an error. + + If `auto_error` is set to `False`, when the HTTP Authorization header + is not available, instead of erroring out, the dependency result will + be `None`. + + This is useful when you want to have optional authentication. + + It is also useful when you want to have authentication that can be + provided in one of multiple optional ways (for example, with OpenID + Connect or in a cookie). + """ + ), + ] = True, + ): + self.model = OpenIdConnectModel( + openIdConnectUrl=openIdConnectUrl, description=description + ) + self.scheme_name = scheme_name or self.__class__.__name__ + self.auto_error = auto_error + + async def __call__(self, request: Request) -> Optional[str]: + authorization = request.headers.get("Authorization") + if not authorization: + if self.auto_error: + raise HTTPException( + status_code=HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + else: + return None + return authorization diff --git a/.venv/Lib/site-packages/fastapi/security/utils.py b/.venv/Lib/site-packages/fastapi/security/utils.py new file mode 100644 index 0000000..fa7a450 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/security/utils.py @@ -0,0 +1,10 @@ +from typing import Optional, Tuple + + +def get_authorization_scheme_param( + authorization_header_value: Optional[str], +) -> Tuple[str, str]: + if not authorization_header_value: + return "", "" + scheme, _, param = authorization_header_value.partition(" ") + return scheme, param diff --git a/.venv/Lib/site-packages/fastapi/staticfiles.py b/.venv/Lib/site-packages/fastapi/staticfiles.py new file mode 100644 index 0000000..299015d --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/staticfiles.py @@ -0,0 +1 @@ +from starlette.staticfiles import StaticFiles as StaticFiles # noqa diff --git a/.venv/Lib/site-packages/fastapi/templating.py b/.venv/Lib/site-packages/fastapi/templating.py new file mode 100644 index 0000000..0cb8684 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/templating.py @@ -0,0 +1 @@ +from starlette.templating import Jinja2Templates as Jinja2Templates # noqa diff --git a/.venv/Lib/site-packages/fastapi/testclient.py b/.venv/Lib/site-packages/fastapi/testclient.py new file mode 100644 index 0000000..4012406 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/testclient.py @@ -0,0 +1 @@ +from starlette.testclient import TestClient as TestClient # noqa diff --git a/.venv/Lib/site-packages/fastapi/types.py b/.venv/Lib/site-packages/fastapi/types.py new file mode 100644 index 0000000..3205654 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/types.py @@ -0,0 +1,10 @@ +import types +from enum import Enum +from typing import Any, Callable, Dict, Set, Type, TypeVar, Union + +from pydantic import BaseModel + +DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) +UnionType = getattr(types, "UnionType", Union) +ModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str] +IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] diff --git a/.venv/Lib/site-packages/fastapi/utils.py b/.venv/Lib/site-packages/fastapi/utils.py new file mode 100644 index 0000000..4c7350f --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/utils.py @@ -0,0 +1,220 @@ +import re +import warnings +from dataclasses import is_dataclass +from typing import ( + TYPE_CHECKING, + Any, + Dict, + MutableMapping, + Optional, + Set, + Type, + Union, + cast, +) +from weakref import WeakKeyDictionary + +import fastapi +from fastapi._compat import ( + PYDANTIC_V2, + BaseConfig, + ModelField, + PydanticSchemaGenerationError, + Undefined, + UndefinedType, + Validator, + lenient_issubclass, +) +from fastapi.datastructures import DefaultPlaceholder, DefaultType +from pydantic import BaseModel, create_model +from pydantic.fields import FieldInfo +from typing_extensions import Literal + +if TYPE_CHECKING: # pragma: nocover + from .routing import APIRoute + +# Cache for `create_cloned_field` +_CLONED_TYPES_CACHE: MutableMapping[Type[BaseModel], Type[BaseModel]] = ( + WeakKeyDictionary() +) + + +def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool: + if status_code is None: + return True + # Ref: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#patterned-fields-1 + if status_code in { + "default", + "1XX", + "2XX", + "3XX", + "4XX", + "5XX", + }: + return True + current_status_code = int(status_code) + return not (current_status_code < 200 or current_status_code in {204, 205, 304}) + + +def get_path_param_names(path: str) -> Set[str]: + return set(re.findall("{(.*?)}", path)) + + +def create_model_field( + name: str, + type_: Any, + class_validators: Optional[Dict[str, Validator]] = None, + default: Optional[Any] = Undefined, + required: Union[bool, UndefinedType] = Undefined, + model_config: Type[BaseConfig] = BaseConfig, + field_info: Optional[FieldInfo] = None, + alias: Optional[str] = None, + mode: Literal["validation", "serialization"] = "validation", +) -> ModelField: + class_validators = class_validators or {} + if PYDANTIC_V2: + field_info = field_info or FieldInfo( + annotation=type_, default=default, alias=alias + ) + else: + field_info = field_info or FieldInfo() + kwargs = {"name": name, "field_info": field_info} + if PYDANTIC_V2: + kwargs.update({"mode": mode}) + else: + kwargs.update( + { + "type_": type_, + "class_validators": class_validators, + "default": default, + "required": required, + "model_config": model_config, + "alias": alias, + } + ) + try: + return ModelField(**kwargs) # type: ignore[arg-type] + except (RuntimeError, PydanticSchemaGenerationError): + raise fastapi.exceptions.FastAPIError( + "Invalid args for response field! Hint: " + f"check that {type_} is a valid Pydantic field type. " + "If you are using a return type annotation that is not a valid Pydantic " + "field (e.g. Union[Response, dict, None]) you can disable generating the " + "response model from the type annotation with the path operation decorator " + "parameter response_model=None. Read more: " + "https://fastapi.tiangolo.com/tutorial/response-model/" + ) from None + + +def create_cloned_field( + field: ModelField, + *, + cloned_types: Optional[MutableMapping[Type[BaseModel], Type[BaseModel]]] = None, +) -> ModelField: + if PYDANTIC_V2: + return field + # cloned_types caches already cloned types to support recursive models and improve + # performance by avoiding unnecessary cloning + if cloned_types is None: + cloned_types = _CLONED_TYPES_CACHE + + original_type = field.type_ + if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"): + original_type = original_type.__pydantic_model__ + use_type = original_type + if lenient_issubclass(original_type, BaseModel): + original_type = cast(Type[BaseModel], original_type) + use_type = cloned_types.get(original_type) + if use_type is None: + use_type = create_model(original_type.__name__, __base__=original_type) + cloned_types[original_type] = use_type + for f in original_type.__fields__.values(): + use_type.__fields__[f.name] = create_cloned_field( + f, cloned_types=cloned_types + ) + new_field = create_model_field(name=field.name, type_=use_type) + new_field.has_alias = field.has_alias # type: ignore[attr-defined] + new_field.alias = field.alias # type: ignore[misc] + new_field.class_validators = field.class_validators # type: ignore[attr-defined] + new_field.default = field.default # type: ignore[misc] + new_field.required = field.required # type: ignore[misc] + new_field.model_config = field.model_config # type: ignore[attr-defined] + new_field.field_info = field.field_info + new_field.allow_none = field.allow_none # type: ignore[attr-defined] + new_field.validate_always = field.validate_always # type: ignore[attr-defined] + if field.sub_fields: # type: ignore[attr-defined] + new_field.sub_fields = [ # type: ignore[attr-defined] + create_cloned_field(sub_field, cloned_types=cloned_types) + for sub_field in field.sub_fields # type: ignore[attr-defined] + ] + if field.key_field: # type: ignore[attr-defined] + new_field.key_field = create_cloned_field( # type: ignore[attr-defined] + field.key_field, # type: ignore[attr-defined] + cloned_types=cloned_types, + ) + new_field.validators = field.validators # type: ignore[attr-defined] + new_field.pre_validators = field.pre_validators # type: ignore[attr-defined] + new_field.post_validators = field.post_validators # type: ignore[attr-defined] + new_field.parse_json = field.parse_json # type: ignore[attr-defined] + new_field.shape = field.shape # type: ignore[attr-defined] + new_field.populate_validators() # type: ignore[attr-defined] + return new_field + + +def generate_operation_id_for_path( + *, name: str, path: str, method: str +) -> str: # pragma: nocover + warnings.warn( + "fastapi.utils.generate_operation_id_for_path() was deprecated, " + "it is not used internally, and will be removed soon", + DeprecationWarning, + stacklevel=2, + ) + operation_id = f"{name}{path}" + operation_id = re.sub(r"\W", "_", operation_id) + operation_id = f"{operation_id}_{method.lower()}" + return operation_id + + +def generate_unique_id(route: "APIRoute") -> str: + operation_id = f"{route.name}{route.path_format}" + operation_id = re.sub(r"\W", "_", operation_id) + assert route.methods + operation_id = f"{operation_id}_{list(route.methods)[0].lower()}" + return operation_id + + +def deep_dict_update(main_dict: Dict[Any, Any], update_dict: Dict[Any, Any]) -> None: + for key, value in update_dict.items(): + if ( + key in main_dict + and isinstance(main_dict[key], dict) + and isinstance(value, dict) + ): + deep_dict_update(main_dict[key], value) + elif ( + key in main_dict + and isinstance(main_dict[key], list) + and isinstance(update_dict[key], list) + ): + main_dict[key] = main_dict[key] + update_dict[key] + else: + main_dict[key] = value + + +def get_value_or_default( + first_item: Union[DefaultPlaceholder, DefaultType], + *extra_items: Union[DefaultPlaceholder, DefaultType], +) -> Union[DefaultPlaceholder, DefaultType]: + """ + Pass items or `DefaultPlaceholder`s by descending priority. + + The first one to _not_ be a `DefaultPlaceholder` will be returned. + + Otherwise, the first item (a `DefaultPlaceholder`) will be returned. + """ + items = (first_item,) + extra_items + for item in items: + if not isinstance(item, DefaultPlaceholder): + return item + return first_item diff --git a/.venv/Lib/site-packages/fastapi/websockets.py b/.venv/Lib/site-packages/fastapi/websockets.py new file mode 100644 index 0000000..55a4ac4 --- /dev/null +++ b/.venv/Lib/site-packages/fastapi/websockets.py @@ -0,0 +1,3 @@ +from starlette.websockets import WebSocket as WebSocket # noqa +from starlette.websockets import WebSocketDisconnect as WebSocketDisconnect # noqa +from starlette.websockets import WebSocketState as WebSocketState # noqa diff --git a/.venv/Lib/site-packages/flask-3.1.1.dist-info/INSTALLER b/.venv/Lib/site-packages/flask-3.1.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/flask-3.1.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/flask-3.1.1.dist-info/METADATA b/.venv/Lib/site-packages/flask-3.1.1.dist-info/METADATA new file mode 100644 index 0000000..42ce213 --- /dev/null +++ b/.venv/Lib/site-packages/flask-3.1.1.dist-info/METADATA @@ -0,0 +1,89 @@ +Metadata-Version: 2.4 +Name: Flask +Version: 3.1.1 +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/.venv/Lib/site-packages/flask-3.1.1.dist-info/RECORD b/.venv/Lib/site-packages/flask-3.1.1.dist-info/RECORD new file mode 100644 index 0000000..3c0c88f --- /dev/null +++ b/.venv/Lib/site-packages/flask-3.1.1.dist-info/RECORD @@ -0,0 +1,58 @@ +../../Scripts/flask.exe,sha256=bcuvkUsz83r-P4UIXZlRQkRQPAyycoTl4d3-u2soRr4,108434 +flask-3.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +flask-3.1.1.dist-info/METADATA,sha256=EsnOyVfBXjw1BkGn-9lrI5mcv1NwYyB4_CfdW2FSDZQ,3014 +flask-3.1.1.dist-info/RECORD,, +flask-3.1.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask-3.1.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +flask-3.1.1.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40 +flask-3.1.1.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=4atDhJJ_cpV1VMq4qsfU4E_61M1oN93jlS2H9gjrl58,15120 +flask/debughelpers.py,sha256=PGIDhStW_efRjpaa3zHIpo-htStJOR41Ip3OJWPYBwo,6080 +flask/globals.py,sha256=XdQZmStBmPIs8t93tjx6pO7Bm3gobAaONWkFcUHaGas,1713 +flask/helpers.py,sha256=7njmzkFJvrPSQudsgONsgQzaGrGppeBINevKgWescPk,23521 +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=Wj9NVGtiR1jvkZ9gSFd91usUlM8H0g06aPVz2sMh4bw,38116 +flask/sansio/blueprints.py,sha256=Tqe-7EkZ-tbWchm8iDoCfD848f0_3nLv6NNjeIPvHwM,24637 +flask/sansio/scaffold.py,sha256=q6wM4Y4aYMGGN_Litsj3PYKpBS3Zvut0xhDmpBEHFdo,30387 +flask/sessions.py,sha256=ED_OV3Jl1emsy7Zntb7aFWxyoynt-PzNY0eFUH-Syo0,15495 +flask/signals.py,sha256=V7lMUww7CqgJ2ThUBn1PiatZtQanOyt7OZpu2GZI-34,750 +flask/templating.py,sha256=2TcXLT85Asflm2W9WOSFxKCmYn5e49w_Jkg9-NaaJWo,7537 +flask/testing.py,sha256=JT9fi_-_qsAXIpC1NSWcp1VrO-QSXgmdq95extfCwEw,10136 +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/.venv/Lib/site-packages/flask-3.1.1.dist-info/REQUESTED b/.venv/Lib/site-packages/flask-3.1.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/flask-3.1.1.dist-info/WHEEL b/.venv/Lib/site-packages/flask-3.1.1.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/.venv/Lib/site-packages/flask-3.1.1.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/.venv/Lib/site-packages/flask-3.1.1.dist-info/entry_points.txt b/.venv/Lib/site-packages/flask-3.1.1.dist-info/entry_points.txt new file mode 100644 index 0000000..eec6733 --- /dev/null +++ b/.venv/Lib/site-packages/flask-3.1.1.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +flask=flask.cli:main + diff --git a/.venv/Lib/site-packages/flask-3.1.1.dist-info/licenses/LICENSE.txt b/.venv/Lib/site-packages/flask-3.1.1.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/.venv/Lib/site-packages/flask-3.1.1.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/.venv/Lib/site-packages/flask/__init__.py b/.venv/Lib/site-packages/flask/__init__.py new file mode 100644 index 0000000..1fdc50c --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/flask/__main__.py b/.venv/Lib/site-packages/flask/__main__.py new file mode 100644 index 0000000..4e28416 --- /dev/null +++ b/.venv/Lib/site-packages/flask/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/.venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7b646d0effcae367581c25c4ba761c88f9d76b9 GIT binary patch literal 2548 zcmZXWTW=Fb6vtO<95eSnY93cg8Spge#Ut%OATz*{7E>Qm1ddr6e8R>!~npShelXJ+I-Q>jh@ z&y&hZ{U@L|_z^sz7D(^XKo5yVG|3>QL?sD1WQ1r~3u_T0Vn%5cw6GR6VrHDi1;>np z*+Dx5$Bj-iNt1#TMwgkQDZw4UX_^+?37nxB!Aan5+AX-t=rMa~uizALmSzQ~fpat` zI0M{A`vi9z{pJ825ZnViNCyS?0uRw4!CBy8IxIK`JVHkV_W_U6QNjJdV{}aL0Prb# zO7I}?I2{)}1T0fo@URgzC+LLW5o6MvqEmuLfv4%T;4$DCIwSZL@GPAbJPw?vdBHO9 z9Gw$90bHO3!IQxAbYAcj@B&>BJPmxBo)$b~ESg1H6g&&OM3)5TfzQx0g6DwG(zAjK z#yNADE(@LqK2Og}goU@agDr6M>qNEF+QRn{dSO4bA1W-qMOQ)clB#9duIlQx`_I)^$hW?As=D&_7zS8~aM7 z$vLy!KvycZ<+3~I&(u{%Q5%h8QWmB52*>1PFbK4UjuOrpKbk1+Fvo$wL5-?qo8#co zDui4I$kmvuh!3VICNxxQ%mKY0b(fh9Lv@*=x-QqtO_zZuWvV+2qHr3J3ltKZX*y>W z9Jj+^mZns710C7c2e(b7)xhTaLUuV*O=VAa>&I3RZ*oJa+8kQMF|m3T8~4DP+Bs1g zcB8?xlVa1U3vu9BOP8rog_@$-d${cWGJ}CJXiLLFw3h{*zM6fMdv>S78dzd*WZJn1 zasyI#kol@^>Fr4~f#$d>cSRhzKspgbPcUse4AB_+pA4!0grqo-dkbrt0m8%u1A)|k zLON<3zx83KC2qBgRl$uLJW+r!A=0}?01wFl+;qy!>H@%DN+Ka0poiHm)YCmL~7%h9Q@k%$aHLf~H%tII;3_@mx{< z%3*Sqscw@q=;*>ehM8oW%X!^wz-ck`a?xb2s;RD8YzHzoiAQEqCx1)yXzizp_L@==zZ_n(dSA(;- z!8x3kuotmZlX)E=cCVQ>|t*x1KH z!L5OnfI?k@`Ij(|Eim7am&wd8pZt}SUvKb|7mHYX|k2elOp`LZ=eT?)>y&M@m zOp?^_dlE~f57Q(!_98p;EIaceyZ9`-_k8puI=q;>-++vJlIszUd)f=brNMC@d!6Or z?sj-wG=^&j?r)BJ*$Y?PJG=vk;~xo*dzp8kBVIG?vT8USw=Ykl$)M>L&N?$1kb2&O zQZJe;njD%wH2r7>& zTGL>k@=f@}lk5Bn<}fTtl20bzklugDuusN)QuN8RPd@g^4WBIgejtNf8jd4jW&MwI> zhzZV2NmU3a%FjwoE-8+2ODryN4DeJaD^_sJNi5Ee@kmV0PAyV!$MD~s{T zOo}PaEJ@WZ07@mMrxwSgfmG_p$AfH+kJl@xyv1RYo1apelWJGQ0kj$9vSMB!@qw9< Sk?}5r=tBmUR<Joy@b%v-^iq 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 WbI5Jv?*^_!jj3zp9~ijG?q30W?m0pL literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c65dba13e3912306406bdea0510e5acecea5545 GIT binary patch literal 5015 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c17b204201d56cce1178b30944545262f50d5dff GIT binary patch literal 43535 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b584b40f5d79a99ab8f019662182115d9b5e86a5 GIT binary patch literal 19842 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d7a438cdef1f779a43ef27a4f58dd93c353a33d GIT binary patch literal 9147 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 J7AR3U`hT4L(-Qyy literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/globals.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/globals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..950404f011066e78bd45e47f7d3fef6b73297baf GIT binary patch literal 1880 zcmb`Hzi%5i6vyxM+d50~4_S_6$7bv_jhjZDvIO~o!1-Y#X<*cLi(Zr-&LSe)}KqWq$4DpXLdqk_Iy~b#({Z>i@uK0c1 zZ9>6U48Ia)fx>5_j%zV`?h1JCF>;T!^eX|*gDMkIS9I{$t{*{{RRfEJ(HC(iuPH)sLjzqfw#J1bWA*Kucx+zebddL)lI1NEC5)^ry zpl#2s%B`8UCEDRQmk>;FTGR=udW;cQ42LSC4T?Cmn*>H%{By*KKwblx?(nF7Lg_tA z)Fmjx&*)`&d8v%niJVUmrd7{@5aBkqDMJor1X(VaPT(weGTTzna0Suo6nc6=0iF!> zeCj8SYSVx)pj*y!x2Wf0!vQ_b8et<)8b2B2AXgeS2i}*P%ly$!8W;Pi;u`eT_oV#$nyX)rH8~qChS|FI^B~6Hk;If zO3M?`V06nSPPsE4irI@|R6)?vkf6N-*YiB}H7ROuFq5yCuMJK0lYKNA2+YId%QWaM z&G~7=*!G0Sh+!~!3;l#Vm~PEi1t=dL*`~=ji%FQ>BW$-ryjt0ocZ$iE(a&sryLQi5 zUb(mYaP{#6CSSX-a#6@UC|6M1Ftt=KJ`WP`Q+o``N?`pUJI!#Zth3?p8q3JVbA~Z= zxgp7%Tz*_O6OsorV28nsN$JkZW=b{%Xns@9pjG~{7P|z2<%7ddp*e}Fs`^@4c%w`lD+_-spZpb#{doI_+sCoeacuE8 zHXpQ?j$;>25{X=_tDR<*#jDSZqlKH@X?k z2_^9FZyr1ZQiyc5{mnj^nui(t>jzhWOhvkC_f|koQvDEDt{q-mIr?n1s~u)OJyKR* u&0T&O%N@+X_<6a~cm0*}bKw8E_)Nyg%jna6Qd}I6q9n8LWM1qd?D{{u$1i^X literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef58098c9829dfb8a94a75f77acd6608ae6a87d8 GIT binary patch literal 25451 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 B*AKlCC+E+lLcz(o?E0eTQD7f5sJn^~?XTN+?@ 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/signals.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/signals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90db923786f2cb2e4f36a1cfee7cc2de5959f9e0 GIT binary patch literal 1237 zcmZ{j%}*0S6u@WOT1rb{u^F4*~?}U6BFaXi_viNF1~;g4u{BAvgJ0B8dhsFDuqQc04$c|?!Yqot?>A^=CvdV5MS9NSCz3I5;J=$Xjj zjZ!>>hy}zz2yrDK5+Ou9AO=H-fq+Pc5Q%_Dg%E=QF%&{110o$lqyl0%gcu44`7oUy z{^>O@FYy#I43i4@P1!G?+nR-2PMxMC7NQ1F4KOz@iq zrmR32B20iIexRZm8r>Cir0;IV2W4z|FVf5+%PN}&!v{ElQH9$1Y|;tL_uvP3OeC{R?zg^G?mW+OkslMq%KtU)k@#5;(5U`N-C8YU#>trC!G zRRigi$Ga0A=tEzh_w;)K=<9P|ANKUQuMd5F?(2(99UbWDb5CDn>*&KkpZDos~(mUY%O8*T8w}kSCtjO|_xp zMKUQ0dw(kZEH1yJk|ccvb3efKKXEWL@!`pF;#=lgd$YA^-+JU^9@}8loxX88dopY1 zm!0VsHkfqBX4=J8(Vn~KjNLz$-SMmKrPh*t^MNz|&;}!JcD8-gI46=!DE23hxVxy$SRa_?o55wHCZb9h!M literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/templating.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/templating.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..845950d9d68101765783840253fc9317ccba7724 GIT binary patch literal 9922 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c26a257de25803f92450179541a6b3f164b91ed8 GIT binary patch literal 13616 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 Ca_h_h literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc b/.venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e13b983e26ac18dde2c3bf9fdf8e39a4274a316f GIT binary patch literal 4145 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 E1 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/.venv/Lib/site-packages/flask/blueprints.py b/.venv/Lib/site-packages/flask/blueprints.py new file mode 100644 index 0000000..b6d4e43 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/flask/cli.py b/.venv/Lib/site-packages/flask/cli.py new file mode 100644 index 0000000..ed11f25 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/flask/config.py b/.venv/Lib/site-packages/flask/config.py new file mode 100644 index 0000000..34ef1a5 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/flask/ctx.py b/.venv/Lib/site-packages/flask/ctx.py new file mode 100644 index 0000000..9b164d3 --- /dev/null +++ b/.venv/Lib/site-packages/flask/ctx.py @@ -0,0 +1,449 @@ +from __future__ import annotations + +import contextvars +import sys +import typing as t +from functools import update_wrapper +from types import TracebackType + +from werkzeug.exceptions import HTTPException + +from . import typing as ft +from .globals import _cv_app +from .globals import _cv_request +from .signals import appcontext_popped +from .signals import appcontext_pushed + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import WSGIEnvironment + + from .app import Flask + from .sessions import SessionMixin + from .wrappers import Request + + +# a singleton sentinel value for parameter defaults +_sentinel = object() + + +class _AppCtxGlobals: + """A plain object. Used as a namespace for storing data during an + application context. + + Creating an app context automatically creates this object, which is + made available as the :data:`g` proxy. + + .. describe:: 'key' in g + + Check whether an attribute is present. + + .. versionadded:: 0.10 + + .. describe:: iter(g) + + Return an iterator over the attribute names. + + .. versionadded:: 0.10 + """ + + # Define attr methods to let mypy know this is a namespace object + # that has arbitrary attributes. + + def __getattr__(self, name: str) -> t.Any: + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def __setattr__(self, name: str, value: t.Any) -> None: + self.__dict__[name] = value + + def __delattr__(self, name: str) -> None: + try: + del self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def get(self, name: str, default: t.Any | None = None) -> t.Any: + """Get an attribute by name, or a default value. Like + :meth:`dict.get`. + + :param name: Name of attribute to get. + :param default: Value to return if the attribute is not present. + + .. versionadded:: 0.10 + """ + return self.__dict__.get(name, default) + + def pop(self, name: str, default: t.Any = _sentinel) -> t.Any: + """Get and remove an attribute by name. Like :meth:`dict.pop`. + + :param name: Name of attribute to pop. + :param default: Value to return if the attribute is not present, + instead of raising a ``KeyError``. + + .. versionadded:: 0.11 + """ + if default is _sentinel: + return self.__dict__.pop(name) + else: + return self.__dict__.pop(name, default) + + def setdefault(self, name: str, default: t.Any = None) -> t.Any: + """Get the value of an attribute if it is present, otherwise + set and return a default value. Like :meth:`dict.setdefault`. + + :param name: Name of attribute to get. + :param default: Value to set and return if the attribute is not + present. + + .. versionadded:: 0.11 + """ + return self.__dict__.setdefault(name, default) + + def __contains__(self, item: str) -> bool: + return item in self.__dict__ + + def __iter__(self) -> t.Iterator[str]: + return iter(self.__dict__) + + def __repr__(self) -> str: + ctx = _cv_app.get(None) + if ctx is not None: + return f"" + return object.__repr__(self) + + +def after_this_request( + f: ft.AfterRequestCallable[t.Any], +) -> ft.AfterRequestCallable[t.Any]: + """Executes a function after this request. This is useful to modify + response objects. The function is passed the response object and has + to return the same or a new one. + + Example:: + + @app.route('/') + def index(): + @after_this_request + def add_header(response): + response.headers['X-Foo'] = 'Parachute' + return response + return 'Hello World!' + + This is more useful if a function other than the view function wants to + modify a response. For instance think of a decorator that wants to add + some headers without converting the return value into a response object. + + .. versionadded:: 0.9 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'after_this_request' can only be used when a request" + " context is active, such as in a view function." + ) + + ctx._after_request_functions.append(f) + return f + + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def copy_current_request_context(f: F) -> F: + """A helper function that decorates a function to retain the current + request context. This is useful when working with greenlets. The moment + the function is decorated a copy of the request context is created and + then pushed when the function is called. The current session is also + included in the copied request context. + + Example:: + + import gevent + from flask import copy_current_request_context + + @app.route('/') + def index(): + @copy_current_request_context + def do_some_work(): + # do some work here, it can access flask.request or + # flask.session like you would otherwise in the view function. + ... + gevent.spawn(do_some_work) + return 'Regular response' + + .. versionadded:: 0.10 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'copy_current_request_context' can only be used when a" + " request context is active, such as in a view function." + ) + + ctx = ctx.copy() + + def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any: + with ctx: # type: ignore[union-attr] + return ctx.app.ensure_sync(f)(*args, **kwargs) # type: ignore[union-attr] + + return update_wrapper(wrapper, f) # type: ignore[return-value] + + +def has_request_context() -> bool: + """If you have code that wants to test if a request context is there or + not this function can be used. For instance, you may want to take advantage + of request information if the request object is available, but fail + silently if it is unavailable. + + :: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and has_request_context(): + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + Alternatively you can also just test any of the context bound objects + (such as :class:`request` or :class:`g`) for truthness:: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and request: + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + .. versionadded:: 0.7 + """ + return _cv_request.get(None) is not None + + +def has_app_context() -> bool: + """Works like :func:`has_request_context` but for the application + context. You can also just do a boolean check on the + :data:`current_app` object instead. + + .. versionadded:: 0.9 + """ + return _cv_app.get(None) is not None + + +class AppContext: + """The app context contains application-specific information. An app + context is created and pushed at the beginning of each request if + one is not already active. An app context is also pushed when + running CLI commands. + """ + + def __init__(self, app: Flask) -> None: + self.app = app + self.url_adapter = app.create_url_adapter(None) + self.g: _AppCtxGlobals = app.app_ctx_globals_class() + self._cv_tokens: list[contextvars.Token[AppContext]] = [] + + def push(self) -> None: + """Binds the app context to the current context.""" + self._cv_tokens.append(_cv_app.set(self)) + appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync) + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the app context.""" + try: + if len(self._cv_tokens) == 1: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) + finally: + ctx = _cv_app.get() + _cv_app.reset(self._cv_tokens.pop()) + + if ctx is not self: + raise AssertionError( + f"Popped wrong app context. ({ctx!r} instead of {self!r})" + ) + + appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync) + + def __enter__(self) -> AppContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + +class RequestContext: + """The request context contains per-request information. The Flask + app creates and pushes it at the beginning of the request, then pops + it at the end of the request. It will create the URL adapter and + request object for the WSGI environment provided. + + Do not attempt to use this class directly, instead use + :meth:`~flask.Flask.test_request_context` and + :meth:`~flask.Flask.request_context` to create this object. + + When the request context is popped, it will evaluate all the + functions registered on the application for teardown execution + (:meth:`~flask.Flask.teardown_request`). + + The request context is automatically popped at the end of the + request. When using the interactive debugger, the context will be + restored so ``request`` is still accessible. Similarly, the test + client can preserve the context after the request ends. However, + teardown functions may already have closed some resources such as + database connections. + """ + + def __init__( + self, + app: Flask, + environ: WSGIEnvironment, + request: Request | None = None, + session: SessionMixin | None = None, + ) -> None: + self.app = app + if request is None: + request = app.request_class(environ) + request.json_module = app.json + self.request: Request = request + self.url_adapter = None + try: + self.url_adapter = app.create_url_adapter(self.request) + except HTTPException as e: + self.request.routing_exception = e + self.flashes: list[tuple[str, str]] | None = None + self.session: SessionMixin | None = session + # Functions that should be executed after the request on the response + # object. These will be called before the regular "after_request" + # functions. + self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = [] + + self._cv_tokens: list[ + tuple[contextvars.Token[RequestContext], AppContext | None] + ] = [] + + def copy(self) -> RequestContext: + """Creates a copy of this request context with the same request object. + This can be used to move a request context to a different greenlet. + Because the actual request object is the same this cannot be used to + move a request context to a different thread unless access to the + request object is locked. + + .. versionadded:: 0.10 + + .. versionchanged:: 1.1 + The current session object is used instead of reloading the original + data. This prevents `flask.session` pointing to an out-of-date object. + """ + return self.__class__( + self.app, + environ=self.request.environ, + request=self.request, + session=self.session, + ) + + def match_request(self) -> None: + """Can be overridden by a subclass to hook into the matching + of the request. + """ + try: + result = self.url_adapter.match(return_rule=True) # type: ignore + self.request.url_rule, self.request.view_args = result # type: ignore + except HTTPException as e: + self.request.routing_exception = e + + def push(self) -> None: + # Before we push the request context we have to ensure that there + # is an application context. + app_ctx = _cv_app.get(None) + + if app_ctx is None or app_ctx.app is not self.app: + app_ctx = self.app.app_context() + app_ctx.push() + else: + app_ctx = None + + self._cv_tokens.append((_cv_request.set(self), app_ctx)) + + # Open the session at the moment that the request context is available. + # This allows a custom open_session method to use the request context. + # Only open a new session if this is the first time the request was + # pushed, otherwise stream_with_context loses the session. + if self.session is None: + session_interface = self.app.session_interface + self.session = session_interface.open_session(self.app, self.request) + + if self.session is None: + self.session = session_interface.make_null_session(self.app) + + # Match the request URL after loading the session, so that the + # session is available in custom URL converters. + if self.url_adapter is not None: + self.match_request() + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the request context and unbinds it by doing that. This will + also trigger the execution of functions registered by the + :meth:`~flask.Flask.teardown_request` decorator. + + .. versionchanged:: 0.9 + Added the `exc` argument. + """ + clear_request = len(self._cv_tokens) == 1 + + try: + if clear_request: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) + + request_close = getattr(self.request, "close", None) + if request_close is not None: + request_close() + finally: + ctx = _cv_request.get() + token, app_ctx = self._cv_tokens.pop() + _cv_request.reset(token) + + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + if clear_request: + ctx.request.environ["werkzeug.request"] = None + + if app_ctx is not None: + app_ctx.pop(exc) + + if ctx is not self: + raise AssertionError( + f"Popped wrong request context. ({ctx!r} instead of {self!r})" + ) + + def __enter__(self) -> RequestContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + def __repr__(self) -> str: + return ( + f"<{type(self).__name__} {self.request.url!r}" + f" [{self.request.method}] of {self.app.name}>" + ) diff --git a/.venv/Lib/site-packages/flask/debughelpers.py b/.venv/Lib/site-packages/flask/debughelpers.py new file mode 100644 index 0000000..2c8c4c4 --- /dev/null +++ b/.venv/Lib/site-packages/flask/debughelpers.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import typing as t + +from jinja2.loaders import BaseLoader +from werkzeug.routing import RequestRedirect + +from .blueprints import Blueprint +from .globals import request_ctx +from .sansio.app import App + +if t.TYPE_CHECKING: + from .sansio.scaffold import Scaffold + from .wrappers import Request + + +class UnexpectedUnicodeError(AssertionError, UnicodeError): + """Raised in places where we want some better error reporting for + unexpected unicode or binary data. + """ + + +class DebugFilesKeyError(KeyError, AssertionError): + """Raised from request.files during debugging. The idea is that it can + provide a better error message than just a generic KeyError/BadRequest. + """ + + def __init__(self, request: Request, key: str) -> None: + form_matches = request.form.getlist(key) + buf = [ + f"You tried to access the file {key!r} in the request.files" + " dictionary but it does not exist. The mimetype for the" + f" request is {request.mimetype!r} instead of" + " 'multipart/form-data' which means that no file contents" + " were transmitted. To fix this error you should provide" + ' enctype="multipart/form-data" in your form.' + ] + if form_matches: + names = ", ".join(repr(x) for x in form_matches) + buf.append( + "\n\nThe browser instead transmitted some file names. " + f"This was submitted: {names}" + ) + self.msg = "".join(buf) + + def __str__(self) -> str: + return self.msg + + +class FormDataRoutingRedirect(AssertionError): + """This exception is raised in debug mode if a routing redirect + would cause the browser to drop the method or body. This happens + when method is not GET, HEAD or OPTIONS and the status code is not + 307 or 308. + """ + + def __init__(self, request: Request) -> None: + exc = request.routing_exception + assert isinstance(exc, RequestRedirect) + buf = [ + f"A request was sent to '{request.url}', but routing issued" + f" a redirect to the canonical URL '{exc.new_url}'." + ] + + if f"{request.base_url}/" == exc.new_url.partition("?")[0]: + buf.append( + " The URL was defined with a trailing slash. Flask" + " will redirect to the URL with a trailing slash if it" + " was accessed without one." + ) + + buf.append( + " Send requests to the canonical URL, or use 307 or 308 for" + " routing redirects. Otherwise, browsers will drop form" + " data.\n\n" + "This exception is only raised in debug mode." + ) + super().__init__("".join(buf)) + + +def attach_enctype_error_multidict(request: Request) -> None: + """Patch ``request.files.__getitem__`` to raise a descriptive error + about ``enctype=multipart/form-data``. + + :param request: The request to patch. + :meta private: + """ + oldcls = request.files.__class__ + + class newcls(oldcls): # type: ignore[valid-type, misc] + def __getitem__(self, key: str) -> t.Any: + try: + return super().__getitem__(key) + except KeyError as e: + if key not in request.form: + raise + + raise DebugFilesKeyError(request, key).with_traceback( + e.__traceback__ + ) from None + + newcls.__name__ = oldcls.__name__ + newcls.__module__ = oldcls.__module__ + request.files.__class__ = newcls + + +def _dump_loader_info(loader: BaseLoader) -> t.Iterator[str]: + yield f"class: {type(loader).__module__}.{type(loader).__name__}" + for key, value in sorted(loader.__dict__.items()): + if key.startswith("_"): + continue + if isinstance(value, (tuple, list)): + if not all(isinstance(x, str) for x in value): + continue + yield f"{key}:" + for item in value: + yield f" - {item}" + continue + elif not isinstance(value, (str, int, float, bool)): + continue + yield f"{key}: {value!r}" + + +def explain_template_loading_attempts( + app: App, + template: str, + attempts: list[ + tuple[ + BaseLoader, + Scaffold, + tuple[str, str | None, t.Callable[[], bool] | None] | None, + ] + ], +) -> None: + """This should help developers understand what failed""" + info = [f"Locating template {template!r}:"] + total_found = 0 + blueprint = None + if request_ctx and request_ctx.request.blueprint is not None: + blueprint = request_ctx.request.blueprint + + for idx, (loader, srcobj, triple) in enumerate(attempts): + if isinstance(srcobj, App): + src_info = f"application {srcobj.import_name!r}" + elif isinstance(srcobj, Blueprint): + src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})" + else: + src_info = repr(srcobj) + + info.append(f"{idx + 1:5}: trying loader of {src_info}") + + for line in _dump_loader_info(loader): + info.append(f" {line}") + + if triple is None: + detail = "no match" + else: + detail = f"found ({triple[1] or ''!r})" + total_found += 1 + info.append(f" -> {detail}") + + seems_fishy = False + if total_found == 0: + info.append("Error: the template could not be found.") + seems_fishy = True + elif total_found > 1: + info.append("Warning: multiple loaders returned a match for the template.") + seems_fishy = True + + if blueprint is not None and seems_fishy: + info.append( + " The template was looked up from an endpoint that belongs" + f" to the blueprint {blueprint!r}." + ) + info.append(" Maybe you did not place a template in the right folder?") + info.append(" See https://flask.palletsprojects.com/blueprints/#templates") + + app.logger.info("\n".join(info)) diff --git a/.venv/Lib/site-packages/flask/globals.py b/.venv/Lib/site-packages/flask/globals.py new file mode 100644 index 0000000..e2c410c --- /dev/null +++ b/.venv/Lib/site-packages/flask/globals.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import typing as t +from contextvars import ContextVar + +from werkzeug.local import LocalProxy + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .ctx import _AppCtxGlobals + from .ctx import AppContext + from .ctx import RequestContext + from .sessions import SessionMixin + from .wrappers import Request + + +_no_app_msg = """\ +Working outside of application context. + +This typically means that you attempted to use functionality that needed +the current application. To solve this, set up an application context +with app.app_context(). See the documentation for more information.\ +""" +_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx") +app_ctx: AppContext = LocalProxy( # type: ignore[assignment] + _cv_app, unbound_message=_no_app_msg +) +current_app: Flask = LocalProxy( # type: ignore[assignment] + _cv_app, "app", unbound_message=_no_app_msg +) +g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment] + _cv_app, "g", unbound_message=_no_app_msg +) + +_no_req_msg = """\ +Working outside of request context. + +This typically means that you attempted to use functionality that needed +an active HTTP request. Consult the documentation on testing for +information about how to avoid this problem.\ +""" +_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx") +request_ctx: RequestContext = LocalProxy( # type: ignore[assignment] + _cv_request, unbound_message=_no_req_msg +) +request: Request = LocalProxy( # type: ignore[assignment] + _cv_request, "request", unbound_message=_no_req_msg +) +session: SessionMixin = LocalProxy( # type: ignore[assignment] + _cv_request, "session", unbound_message=_no_req_msg +) diff --git a/.venv/Lib/site-packages/flask/helpers.py b/.venv/Lib/site-packages/flask/helpers.py new file mode 100644 index 0000000..a6b7e15 --- /dev/null +++ b/.venv/Lib/site-packages/flask/helpers.py @@ -0,0 +1,634 @@ +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_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]]: + """Request contexts disappear when the response is started on the server. + This is done for efficiency reasons and to make it less likely to encounter + memory leaks with badly written WSGI middlewares. The downside is that if + you are using streamed responses, the generator cannot access request bound + information any more. + + This function however can help you keep the context around for longer:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + @stream_with_context + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(generate()) + + Alternatively it can also be used around a specific generator:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(stream_with_context(generate())) + + .. versionadded:: 0.9 + """ + try: + gen = iter(generator_or_function) # type: ignore[arg-type] + except TypeError: + + def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any: + gen = generator_or_function(*args, **kwargs) # type: ignore[operator] + return stream_with_context(gen) + + return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type] + + def generator() -> t.Iterator[t.AnyStr | None]: + ctx = _cv_request.get(None) + if ctx is None: + raise RuntimeError( + "'stream_with_context' can only be used when a request" + " context is active, such as in a view function." + ) + with ctx: + # Dummy sentinel. Has to be inside the context block or we're + # not actually keeping the context around. + yield None + + # The try/finally is here so that if someone passes a WSGI level + # iterator in we're still running the cleanup logic. Generators + # don't need that because they are closed on their destruction + # automatically. + try: + yield from gen + finally: + if hasattr(gen, "close"): + gen.close() + + # The trick is to start the generator. Then the code execution runs until + # the first dummy None is yielded at which point the context was already + # pushed. This item is discarded. Then when the iteration continues the + # real generator is executed. + wrapped_g = generator() + next(wrapped_g) + return wrapped_g # type: ignore[return-value] + + +def make_response(*args: t.Any) -> Response: + """Sometimes it is necessary to set additional headers in a view. Because + views do not have to return response objects but can return a value that + is converted into a response object by Flask itself, it becomes tricky to + add headers to it. This function can be called instead of using a return + and you will get a response object which you can use to attach headers. + + If view looked like this and you want to add a new header:: + + def index(): + return render_template('index.html', foo=42) + + You can now do something like this:: + + def index(): + response = make_response(render_template('index.html', foo=42)) + response.headers['X-Parachutes'] = 'parachutes are cool' + return response + + This function accepts the very same arguments you can return from a + view function. This for example creates a response with a 404 error + code:: + + response = make_response(render_template('not_found.html'), 404) + + The other use case of this function is to force the return value of a + view function into a response which is helpful with view + decorators:: + + response = make_response(view_function()) + response.headers['X-Parachutes'] = 'parachutes are cool' + + Internally this function does the following things: + + - if no arguments are passed, it creates a new response argument + - if one argument is passed, :meth:`flask.Flask.make_response` + is invoked with it. + - if more than one argument is passed, the arguments are passed + to the :meth:`flask.Flask.make_response` function as tuple. + + .. versionadded:: 0.6 + """ + if not args: + return current_app.response_class() + if len(args) == 1: + args = args[0] + return current_app.make_response(args) + + +def url_for( + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, +) -> str: + """Generate a URL to the given endpoint with the given values. + + This requires an active request or application context, and calls + :meth:`current_app.url_for() `. See that method + for full documentation. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it is + external. + :param _external: If given, prefer the URL to be internal (False) or + require it to be external (True). External URLs include the + scheme and domain. When not in an active request, URLs are + external by default. + :param values: Values to use for the variable parts of the URL rule. + Unknown keys are appended as query string arguments, like + ``?a=b&c=d``. + + .. versionchanged:: 2.2 + Calls ``current_app.url_for``, allowing an app to override the + behavior. + + .. versionchanged:: 0.10 + The ``_scheme`` parameter was added. + + .. versionchanged:: 0.9 + The ``_anchor`` and ``_method`` parameters were added. + + .. versionchanged:: 0.9 + Calls ``app.handle_url_build_error`` on build errors. + """ + return current_app.url_for( + endpoint, + _anchor=_anchor, + _method=_method, + _scheme=_scheme, + _external=_external, + **values, + ) + + +def redirect( + location: str, code: int = 302, Response: type[BaseResponse] | None = None +) -> BaseResponse: + """Create a redirect response object. + + If :data:`~flask.current_app` is available, it will use its + :meth:`~flask.Flask.redirect` method, otherwise it will use + :func:`werkzeug.utils.redirect`. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + :param Response: The response class to use. Not used when + ``current_app`` is active, which uses ``app.response_class``. + + .. versionadded:: 2.2 + Calls ``current_app.redirect`` if available instead of always + using Werkzeug's default ``redirect``. + """ + if current_app: + return current_app.redirect(location, code=code) + + return _wz_redirect(location, code=code, Response=Response) + + +def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given + status code. + + If :data:`~flask.current_app` is available, it will call its + :attr:`~flask.Flask.aborter` object, otherwise it will use + :func:`werkzeug.exceptions.abort`. + + :param code: The status code for the exception, which must be + registered in ``app.aborter``. + :param args: Passed to the exception. + :param kwargs: Passed to the exception. + + .. versionadded:: 2.2 + Calls ``current_app.aborter`` if available instead of always + using Werkzeug's default ``abort``. + """ + if current_app: + current_app.aborter(code, *args, **kwargs) + + _wz_abort(code, *args, **kwargs) + + +def get_template_attribute(template_name: str, attribute: str) -> t.Any: + """Loads a macro (or variable) a template exports. This can be used to + invoke a macro from within Python code. If you for example have a + template named :file:`_cider.html` with the following contents: + + .. sourcecode:: html+jinja + + {% macro hello(name) %}Hello {{ name }}!{% endmacro %} + + You can access this from Python code like this:: + + hello = get_template_attribute('_cider.html', 'hello') + return hello('World') + + .. versionadded:: 0.2 + + :param template_name: the name of the template + :param attribute: the name of the variable of macro to access + """ + return getattr(current_app.jinja_env.get_template(template_name).module, attribute) + + +def flash(message: str, category: str = "message") -> None: + """Flashes a message to the next request. In order to remove the + flashed message from the session and to display it to the user, + the template has to call :func:`get_flashed_messages`. + + .. versionchanged:: 0.3 + `category` parameter added. + + :param message: the message to be flashed. + :param category: the category for the message. The following values + are recommended: ``'message'`` for any kind of message, + ``'error'`` for errors, ``'info'`` for information + messages and ``'warning'`` for warnings. However any + kind of string can be used as category. + """ + # Original implementation: + # + # session.setdefault('_flashes', []).append((category, message)) + # + # This assumed that changes made to mutable structures in the session are + # always in sync with the session object, which is not true for session + # implementations that use external storage for keeping their keys/values. + flashes = session.get("_flashes", []) + flashes.append((category, message)) + session["_flashes"] = flashes + app = current_app._get_current_object() # type: ignore + message_flashed.send( + app, + _async_wrapper=app.ensure_sync, + message=message, + category=category, + ) + + +def get_flashed_messages( + with_categories: bool = False, category_filter: t.Iterable[str] = () +) -> list[str] | list[tuple[str, str]]: + """Pulls all flashed messages from the session and returns them. + Further calls in the same request to the function will return + the same messages. By default just the messages are returned, + but when `with_categories` is set to ``True``, the return value will + be a list of tuples in the form ``(category, message)`` instead. + + Filter the flashed messages to one or more categories by providing those + categories in `category_filter`. This allows rendering categories in + separate html blocks. The `with_categories` and `category_filter` + arguments are distinct: + + * `with_categories` controls whether categories are returned with message + text (``True`` gives a tuple, where ``False`` gives just the message text). + * `category_filter` filters the messages down to only those matching the + provided categories. + + See :doc:`/patterns/flashing` for examples. + + .. versionchanged:: 0.3 + `with_categories` parameter added. + + .. versionchanged:: 0.9 + `category_filter` parameter added. + + :param with_categories: set to ``True`` to also receive categories. + :param category_filter: filter of categories to limit return values. Only + categories in the list will be returned. + """ + flashes = request_ctx.flashes + if flashes is None: + flashes = session.pop("_flashes") if "_flashes" in session else [] + request_ctx.flashes = flashes + if category_filter: + flashes = list(filter(lambda f: f[0] in category_filter, flashes)) + if not with_categories: + return [x[1] for x in flashes] + return flashes + + +def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]: + if kwargs.get("max_age") is None: + kwargs["max_age"] = current_app.get_send_file_max_age + + kwargs.update( + environ=request.environ, + use_x_sendfile=current_app.config["USE_X_SENDFILE"], + response_class=current_app.response_class, + _root_path=current_app.root_path, # type: ignore + ) + return kwargs + + +def send_file( + path_or_file: os.PathLike[t.AnyStr] | str | t.BinaryIO, + mimetype: str | None = None, + as_attachment: bool = False, + download_name: str | None = None, + conditional: bool = True, + etag: bool | str = True, + last_modified: datetime | int | float | None = None, + max_age: None | (int | t.Callable[[str | None], int | None]) = None, +) -> Response: + """Send the contents of a file to the client. + + The first argument can be a file path or a file-like object. Paths + are preferred in most cases because Werkzeug can manage the file and + get extra information from the path. Passing a file-like object + requires that the file is opened in binary mode, and is mostly + useful when building a file in memory with :class:`io.BytesIO`. + + Never pass file paths provided by a user. The path is assumed to be + trusted, so a user could craft a path to access a file you didn't + intend. Use :func:`send_from_directory` to safely serve + user-requested paths from within a directory. + + If the WSGI server sets a ``file_wrapper`` in ``environ``, it is + used, otherwise Werkzeug's built-in wrapper is used. Alternatively, + if the HTTP server supports ``X-Sendfile``, configuring Flask with + ``USE_X_SENDFILE = True`` will tell the server to send the given + path, which is much more efficient than reading it in Python. + + :param path_or_file: The path to the file to send, relative to the + current working directory if a relative path is given. + Alternatively, a file-like object opened in binary mode. Make + sure the file pointer is seeked to the start of the data. + :param mimetype: The MIME type to send for the file. If not + provided, it will try to detect it from the file name. + :param as_attachment: Indicate to a browser that it should offer to + save the file instead of displaying it. + :param download_name: The default name browsers will use when saving + the file. Defaults to the passed file name. + :param conditional: Enable conditional and range responses based on + request headers. Requires passing a file path and ``environ``. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. + :param last_modified: The last modified time to send for the file, + in seconds. If not provided, it will try to detect it from the + file path. + :param max_age: How long the client should cache the file, in + seconds. If set, ``Cache-Control`` will be ``public``, otherwise + it will be ``no-cache`` to prefer conditional caching. + + .. versionchanged:: 2.0 + ``download_name`` replaces the ``attachment_filename`` + parameter. If ``as_attachment=False``, it is passed with + ``Content-Disposition: inline`` instead. + + .. versionchanged:: 2.0 + ``max_age`` replaces the ``cache_timeout`` parameter. + ``conditional`` is enabled and ``max_age`` is not set by + default. + + .. versionchanged:: 2.0 + ``etag`` replaces the ``add_etags`` parameter. It can be a + string to use instead of generating one. + + .. versionchanged:: 2.0 + Passing a file-like object that inherits from + :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather + than sending an empty file. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionchanged:: 1.1 + ``filename`` may be a :class:`~os.PathLike` object. + + .. versionchanged:: 1.1 + Passing a :class:`~io.BytesIO` object supports range requests. + + .. versionchanged:: 1.0.3 + Filenames are encoded with ASCII instead of Latin-1 for broader + compatibility with WSGI servers. + + .. versionchanged:: 1.0 + UTF-8 filenames as specified in :rfc:`2231` are supported. + + .. versionchanged:: 0.12 + The filename is no longer automatically inferred from file + objects. If you want to use automatic MIME and etag support, + pass a filename via ``filename_or_fp`` or + ``attachment_filename``. + + .. versionchanged:: 0.12 + ``attachment_filename`` is preferred over ``filename`` for MIME + detection. + + .. versionchanged:: 0.9 + ``cache_timeout`` defaults to + :meth:`Flask.get_send_file_max_age`. + + .. versionchanged:: 0.7 + MIME guessing and etag support for file-like objects was + removed because it was unreliable. Pass a filename if you are + able to, otherwise attach an etag yourself. + + .. versionchanged:: 0.5 + The ``add_etags``, ``cache_timeout`` and ``conditional`` + parameters were added. The default behavior is to add etags. + + .. versionadded:: 0.2 + """ + return werkzeug.utils.send_file( # type: ignore[return-value] + **_prepare_send_file_kwargs( + path_or_file=path_or_file, + environ=request.environ, + mimetype=mimetype, + as_attachment=as_attachment, + download_name=download_name, + conditional=conditional, + etag=etag, + last_modified=last_modified, + max_age=max_age, + ) + ) + + +def send_from_directory( + directory: os.PathLike[str] | str, + path: os.PathLike[str] | str, + **kwargs: t.Any, +) -> Response: + """Send a file from within a directory using :func:`send_file`. + + .. code-block:: python + + @app.route("/uploads/") + def download_file(name): + return send_from_directory( + app.config['UPLOAD_FOLDER'], name, as_attachment=True + ) + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + raises a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under, + relative to the current application's root path. 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/.venv/Lib/site-packages/flask/json/__init__.py b/.venv/Lib/site-packages/flask/json/__init__.py new file mode 100644 index 0000000..c0941d0 --- /dev/null +++ b/.venv/Lib/site-packages/flask/json/__init__.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import json as _json +import typing as t + +from ..globals import current_app +from .provider import _default + +if t.TYPE_CHECKING: # pragma: no cover + from ..wrappers import Response + + +def dumps(obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dumps() ` + method, otherwise it will use :func:`json.dumps`. + + :param obj: The data to serialize. + :param kwargs: Arguments passed to the ``dumps`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dumps``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.dumps(obj, **kwargs) + + kwargs.setdefault("default", _default) + return _json.dumps(obj, **kwargs) + + +def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dump() ` + method, otherwise it will use :func:`json.dump`. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: Arguments passed to the ``dump`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dump``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + Writing to a binary file, and the ``encoding`` argument, will be + removed in Flask 2.1. + """ + if current_app: + current_app.json.dump(obj, fp, **kwargs) + else: + kwargs.setdefault("default", _default) + _json.dump(obj, fp, **kwargs) + + +def loads(s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.loads() ` + method, otherwise it will use :func:`json.loads`. + + :param s: Text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``loads`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.loads``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The data must be a + string or UTF-8 bytes. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.loads(s, **kwargs) + + return _json.loads(s, **kwargs) + + +def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.load() ` + method, otherwise it will use :func:`json.load`. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``load`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.load``, allowing an app to override + the behavior. + + .. versionchanged:: 2.2 + The ``app`` parameter will be removed in Flask 2.3. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The file must be text + mode, or binary mode with UTF-8 bytes. + """ + if current_app: + return current_app.json.load(fp, **kwargs) + + return _json.load(fp, **kwargs) + + +def jsonify(*args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. A dict or list returned from a view will be converted to a + JSON response automatically without needing to call this. + + This requires an active request or application context, and calls + :meth:`app.json.response() `. + + In debug mode, the output is formatted with indentation to make it + easier to read. This may also be controlled by the provider. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + + .. versionchanged:: 2.2 + Calls ``current_app.json.response``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 0.11 + Added support for serializing top-level arrays. This was a + security risk in ancient browsers. See :ref:`security-json`. + + .. versionadded:: 0.2 + """ + return current_app.json.response(*args, **kwargs) # type: ignore[return-value] diff --git a/.venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2737e8ae9eaa67bc7d2545196538481c3716adc5 GIT binary patch literal 6708 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc b/.venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99ba1f63de359c2332b5a582abde02014055a8ca GIT binary patch literal 13970 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/json/provider.py b/.venv/Lib/site-packages/flask/json/provider.py new file mode 100644 index 0000000..ea7e475 --- /dev/null +++ b/.venv/Lib/site-packages/flask/json/provider.py @@ -0,0 +1,215 @@ +from __future__ import annotations + +import dataclasses +import decimal +import json +import typing as t +import uuid +import weakref +from datetime import date + +from werkzeug.http import http_date + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.sansio.response import Response + + from ..sansio.app import App + + +class JSONProvider: + """A standard set of JSON operations for an application. Subclasses + of this can be used to customize JSON behavior or use different + JSON libraries. + + To implement a provider for a specific library, subclass this base + class and implement at least :meth:`dumps` and :meth:`loads`. All + other methods have default implementations. + + To use a different provider, either subclass ``Flask`` and set + :attr:`~flask.Flask.json_provider_class` to a provider class, or set + :attr:`app.json ` to an instance of the class. + + :param app: An application instance. This will be stored as a + :class:`weakref.proxy` on the :attr:`_app` attribute. + + .. versionadded:: 2.2 + """ + + def __init__(self, app: App) -> None: + self._app: App = weakref.proxy(app) + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + :param obj: The data to serialize. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: May be passed to the underlying JSON library. + """ + fp.write(self.dumps(obj, **kwargs)) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + :param s: Text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + return self.loads(fp.read(), **kwargs) + + def _prepare_response_obj( + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] + ) -> t.Any: + if args and kwargs: + raise TypeError("app.json.response() takes either args or kwargs, not both") + + if not args and not kwargs: + return None + + if len(args) == 1: + return args[0] + + return args or kwargs + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. + + The :func:`~flask.json.jsonify` function calls this method for + the current application. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + return self._app.response_class(self.dumps(obj), mimetype="application/json") + + +def _default(o: t.Any) -> t.Any: + if isinstance(o, date): + return http_date(o) + + if isinstance(o, (decimal.Decimal, uuid.UUID)): + return str(o) + + if dataclasses and dataclasses.is_dataclass(o): + return dataclasses.asdict(o) # type: ignore[arg-type] + + if hasattr(o, "__html__"): + return str(o.__html__()) + + raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") + + +class DefaultJSONProvider(JSONProvider): + """Provide JSON operations using Python's built-in :mod:`json` + library. Serializes the following additional data types: + + - :class:`datetime.datetime` and :class:`datetime.date` are + serialized to :rfc:`822` strings. This is the same as the HTTP + date format. + - :class:`uuid.UUID` is serialized to a string. + - :class:`dataclasses.dataclass` is passed to + :func:`dataclasses.asdict`. + - :class:`~markupsafe.Markup` (or any object with a ``__html__`` + method) will call the ``__html__`` method to get a string. + """ + + default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment] + """Apply this function to any object that :meth:`json.dumps` does + not know how to serialize. It should return a valid JSON type or + raise a ``TypeError``. + """ + + ensure_ascii = True + """Replace non-ASCII characters with escape sequences. This may be + more compatible with some clients, but can be disabled for better + performance and size. + """ + + sort_keys = True + """Sort the keys in any serialized dicts. This may be useful for + some caching situations, but can be disabled for better performance. + When enabled, keys must all be strings, they are not converted + before sorting. + """ + + compact: bool | None = None + """If ``True``, or ``None`` out of debug mode, the :meth:`response` + output will not add indentation, newlines, or spaces. If ``False``, + or ``None`` in debug mode, it will use a non-compact representation. + """ + + mimetype = "application/json" + """The mimetype set in :meth:`response`.""" + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON to a string. + + Keyword arguments are passed to :func:`json.dumps`. Sets some + parameter defaults from the :attr:`default`, + :attr:`ensure_ascii`, and :attr:`sort_keys` attributes. + + :param obj: The data to serialize. + :param kwargs: Passed to :func:`json.dumps`. + """ + kwargs.setdefault("default", self.default) + kwargs.setdefault("ensure_ascii", self.ensure_ascii) + kwargs.setdefault("sort_keys", self.sort_keys) + return json.dumps(obj, **kwargs) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON from a string or bytes. + + :param s: Text or UTF-8 bytes. + :param kwargs: Passed to :func:`json.loads`. + """ + return json.loads(s, **kwargs) + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with it. The response mimetype + will be "application/json" and can be changed with + :attr:`mimetype`. + + If :attr:`compact` is ``False`` or debug mode is enabled, the + output will be formatted to be easier to read. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + dump_args: dict[str, t.Any] = {} + + if (self.compact is None and self._app.debug) or self.compact is False: + dump_args.setdefault("indent", 2) + else: + dump_args.setdefault("separators", (",", ":")) + + return self._app.response_class( + f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype + ) diff --git a/.venv/Lib/site-packages/flask/json/tag.py b/.venv/Lib/site-packages/flask/json/tag.py new file mode 100644 index 0000000..8dc3629 --- /dev/null +++ b/.venv/Lib/site-packages/flask/json/tag.py @@ -0,0 +1,327 @@ +""" +Tagged JSON +~~~~~~~~~~~ + +A compact representation for lossless serialization of non-standard JSON +types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this +to serialize the session data, but it may be useful in other places. It +can be extended to support other types. + +.. autoclass:: TaggedJSONSerializer + :members: + +.. autoclass:: JSONTag + :members: + +Let's see an example that adds support for +:class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so +to handle this we will dump the items as a list of ``[key, value]`` +pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to +identify the type. The session serializer processes dicts first, so +insert the new tag at the front of the order since ``OrderedDict`` must +be processed before ``dict``. + +.. code-block:: python + + from flask.json.tag import JSONTag + + class TagOrderedDict(JSONTag): + __slots__ = ('serializer',) + key = ' od' + + def check(self, value): + return isinstance(value, OrderedDict) + + def to_json(self, value): + return [[k, self.serializer.tag(v)] for k, v in iteritems(value)] + + def to_python(self, value): + return OrderedDict(value) + + app.session_interface.serializer.register(TagOrderedDict, index=0) +""" + +from __future__ import annotations + +import typing as t +from base64 import b64decode +from base64 import b64encode +from datetime import datetime +from uuid import UUID + +from markupsafe import Markup +from werkzeug.http import http_date +from werkzeug.http import parse_date + +from ..json import dumps +from ..json import loads + + +class JSONTag: + """Base class for defining type tags for :class:`TaggedJSONSerializer`.""" + + __slots__ = ("serializer",) + + #: The tag to mark the serialized object with. If empty, this tag is + #: only used as an intermediate step during tagging. + key: str = "" + + def __init__(self, serializer: TaggedJSONSerializer) -> None: + """Create a tagger for the given serializer.""" + self.serializer = serializer + + def check(self, value: t.Any) -> bool: + """Check if the given value should be tagged by this tag.""" + raise NotImplementedError + + def to_json(self, value: t.Any) -> t.Any: + """Convert the Python object to an object that is a valid JSON type. + The tag will be added later.""" + raise NotImplementedError + + def to_python(self, value: t.Any) -> t.Any: + """Convert the JSON representation back to the correct type. The tag + will already be removed.""" + raise NotImplementedError + + def tag(self, value: t.Any) -> dict[str, t.Any]: + """Convert the value to a valid JSON type and add the tag structure + around it.""" + return {self.key: self.to_json(value)} + + +class TagDict(JSONTag): + """Tag for 1-item dicts whose only key matches a registered tag. + + Internally, the dict key is suffixed with `__`, and the suffix is removed + when deserializing. + """ + + __slots__ = () + key = " di" + + def check(self, value: t.Any) -> bool: + return ( + isinstance(value, dict) + and len(value) == 1 + and next(iter(value)) in self.serializer.tags + ) + + def to_json(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {f"{key}__": self.serializer.tag(value[key])} + + def to_python(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {key[:-2]: value[key]} + + +class PassDict(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, dict) + + def to_json(self, value: t.Any) -> t.Any: + # JSON objects may only have string keys, so don't bother tagging the + # key here. + return {k: self.serializer.tag(v) for k, v in value.items()} + + tag = to_json + + +class TagTuple(JSONTag): + __slots__ = () + key = " t" + + def check(self, value: t.Any) -> bool: + return isinstance(value, tuple) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + def to_python(self, value: t.Any) -> t.Any: + return tuple(value) + + +class PassList(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, list) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + tag = to_json + + +class TagBytes(JSONTag): + __slots__ = () + key = " b" + + def check(self, value: t.Any) -> bool: + return isinstance(value, bytes) + + def to_json(self, value: t.Any) -> t.Any: + return b64encode(value).decode("ascii") + + def to_python(self, value: t.Any) -> t.Any: + return b64decode(value) + + +class TagMarkup(JSONTag): + """Serialize anything matching the :class:`~markupsafe.Markup` API by + having a ``__html__`` method to the result of that method. Always + deserializes to an instance of :class:`~markupsafe.Markup`.""" + + __slots__ = () + key = " m" + + def check(self, value: t.Any) -> bool: + return callable(getattr(value, "__html__", None)) + + def to_json(self, value: t.Any) -> t.Any: + return str(value.__html__()) + + def to_python(self, value: t.Any) -> t.Any: + return Markup(value) + + +class TagUUID(JSONTag): + __slots__ = () + key = " u" + + def check(self, value: t.Any) -> bool: + return isinstance(value, UUID) + + def to_json(self, value: t.Any) -> t.Any: + return value.hex + + def to_python(self, value: t.Any) -> t.Any: + return UUID(value) + + +class TagDateTime(JSONTag): + __slots__ = () + key = " d" + + def check(self, value: t.Any) -> bool: + return isinstance(value, datetime) + + def to_json(self, value: t.Any) -> t.Any: + return http_date(value) + + def to_python(self, value: t.Any) -> t.Any: + return parse_date(value) + + +class TaggedJSONSerializer: + """Serializer that uses a tag system to compactly represent objects that + are not JSON types. Passed as the intermediate serializer to + :class:`itsdangerous.Serializer`. + + The following extra types are supported: + + * :class:`dict` + * :class:`tuple` + * :class:`bytes` + * :class:`~markupsafe.Markup` + * :class:`~uuid.UUID` + * :class:`~datetime.datetime` + """ + + __slots__ = ("tags", "order") + + #: Tag classes to bind when creating the serializer. Other tags can be + #: added later using :meth:`~register`. + default_tags = [ + TagDict, + PassDict, + TagTuple, + PassList, + TagBytes, + TagMarkup, + TagUUID, + TagDateTime, + ] + + def __init__(self) -> None: + self.tags: dict[str, JSONTag] = {} + self.order: list[JSONTag] = [] + + for cls in self.default_tags: + self.register(cls) + + def register( + self, + tag_class: type[JSONTag], + force: bool = False, + index: int | None = None, + ) -> None: + """Register a new tag with this serializer. + + :param tag_class: tag class to register. Will be instantiated with this + serializer instance. + :param force: overwrite an existing tag. If false (default), a + :exc:`KeyError` is raised. + :param index: index to insert the new tag in the tag order. Useful when + the new tag is a special case of an existing tag. If ``None`` + (default), the tag is appended to the end of the order. + + :raise KeyError: if the tag key is already registered and ``force`` is + not true. + """ + tag = tag_class(self) + key = tag.key + + if key: + if not force and key in self.tags: + raise KeyError(f"Tag '{key}' is already registered.") + + self.tags[key] = tag + + if index is None: + self.order.append(tag) + else: + self.order.insert(index, tag) + + def tag(self, value: t.Any) -> t.Any: + """Convert a value to a tagged representation if necessary.""" + for tag in self.order: + if tag.check(value): + return tag.tag(value) + + return value + + def untag(self, value: dict[str, t.Any]) -> t.Any: + """Convert a tagged representation back to the original type.""" + if len(value) != 1: + return value + + key = next(iter(value)) + + if key not in self.tags: + return value + + return self.tags[key].to_python(value[key]) + + def _untag_scan(self, value: t.Any) -> t.Any: + if isinstance(value, dict): + # untag each item recursively + value = {k: self._untag_scan(v) for k, v in value.items()} + # untag the dict itself + value = self.untag(value) + elif isinstance(value, list): + # untag each item recursively + value = [self._untag_scan(item) for item in value] + + return value + + def dumps(self, value: t.Any) -> str: + """Tag the value and dump it to a compact JSON string.""" + return dumps(self.tag(value), separators=(",", ":")) + + def loads(self, value: str) -> t.Any: + """Load data from a JSON string and deserialized any tagged objects.""" + return self._untag_scan(loads(value)) diff --git a/.venv/Lib/site-packages/flask/logging.py b/.venv/Lib/site-packages/flask/logging.py new file mode 100644 index 0000000..0cb8f43 --- /dev/null +++ b/.venv/Lib/site-packages/flask/logging.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import logging +import sys +import typing as t + +from werkzeug.local import LocalProxy + +from .globals import request + +if t.TYPE_CHECKING: # pragma: no cover + from .sansio.app import App + + +@LocalProxy +def wsgi_errors_stream() -> t.TextIO: + """Find the most appropriate error stream for the application. If a request + is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``. + + If you configure your own :class:`logging.StreamHandler`, you may want to + use this for the stream. If you are using file or dict configuration and + can't import this directly, you can refer to it as + ``ext://flask.logging.wsgi_errors_stream``. + """ + if request: + return request.environ["wsgi.errors"] # type: ignore[no-any-return] + + return sys.stderr + + +def has_level_handler(logger: logging.Logger) -> bool: + """Check if there is a handler in the logging chain that will handle the + given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`. + """ + level = logger.getEffectiveLevel() + current = logger + + while current: + if any(handler.level <= level for handler in current.handlers): + return True + + if not current.propagate: + break + + current = current.parent # type: ignore + + return False + + +#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format +#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``. +default_handler = logging.StreamHandler(wsgi_errors_stream) # type: ignore +default_handler.setFormatter( + logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s") +) + + +def create_logger(app: App) -> logging.Logger: + """Get the Flask app's logger and configure it if needed. + + The logger name will be the same as + :attr:`app.import_name `. + + When :attr:`~flask.Flask.debug` is enabled, set the logger level to + :data:`logging.DEBUG` if it is not set. + + If there is no handler for the logger's effective level, add a + :class:`~logging.StreamHandler` for + :func:`~flask.logging.wsgi_errors_stream` with a basic format. + """ + logger = logging.getLogger(app.name) + + if app.debug and not logger.level: + logger.setLevel(logging.DEBUG) + + if not has_level_handler(logger): + logger.addHandler(default_handler) + + return logger diff --git a/.venv/Lib/site-packages/flask/py.typed b/.venv/Lib/site-packages/flask/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/flask/sansio/README.md b/.venv/Lib/site-packages/flask/sansio/README.md new file mode 100644 index 0000000..623ac19 --- /dev/null +++ b/.venv/Lib/site-packages/flask/sansio/README.md @@ -0,0 +1,6 @@ +# Sansio + +This folder contains code that can be used by alternative Flask +implementations, for example Quart. The code therefore cannot do any +IO, nor be part of a likely IO path. Finally this code cannot use the +Flask globals. diff --git a/.venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc b/.venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..050d9dc3752e3edb4bdc8baed949bc05a9534ef9 GIT binary patch literal 33702 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 AumAu6 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc b/.venv/Lib/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7492235db7ea5cac43eb933d1ded084a753cb57 GIT binary patch literal 31199 zcmd^o3ve4}e%~%0BtU=!_yV6Imm(>WA_eMY%c3dCdf9rJk|jzPOY)Hr7L-7d0KEVu z69?A1t7lB6mzr$ih-%I;uSt4T?Z&!v+UTZ}p3`Y>oM~o)6{!ObXE(}RJCk(U8QQXw z-c6hK_y6_@5R`Ryaqc=@O1s~F@5lf9zrV$QDlIMHaJ?JsyRv7P<9*okoI`BD}xLAdOE84J4*_MG?dT*x*mXk9U$QW5HLkCl&CoUdScHW_JJ zNG!d~ib<5VG>}jKn7BZFH zY~h~c#Jblwu~dhbZWdRMxQ0;KJJ?Tjo$r~lbT$4J1-tmHD-el9v;yw(ndBH~0e9ElH&NYU}3 zYvIuKp^?c*5Lul=u}~x)j)cbW;y4owjEqFbM1)m3}r6dH+2AuWmWh9oH}4P6OD z#IcZ+Eg2k&hXRrqy&lOnQh0n~j0O~pM&hAY;zJWsG#HA-qEfcplsXa~LxVX{m&c-) z0%O#GUPm0A$(9WcO-f@!>a@kOwaWXo07in^F&edjAkDaTscXSMJb{>-T!=f*iySZ; zf8Ofk*8TybSzfc9w^52%R3N=rw7q6OZ=bSuIixaFnfM=r7->`rNK?WU%y&oF-(5Wf;C5pZF_$HGAd8Gb?79}&WlSUeC3hJ@${ zGKFJ8a4Zmu2^5e*m{U=>G^LmBqlN7o{s$w-bJedetA9LjH3W#j;trvv;VJ#%`@2E9S+mrSiK9tn!)beoL zD5H7CJB27e+SGghQ?zN(+bl{Jt=pzMw>{P-@}o_fC3gK3ZCaE3C_z`=rQsXYTC64g z6t&t!yQv0;Rzti}3#~JsRa(07)aC>|acQwA?bE`>bCgJH-t|-T8T^3OXRS@1o8r10 z16gNm@{$-G4}>Gx62c6?0VCm8vbIQIJd}0OU5nzfZZqn*&2^P#t=M*1r@ke##nA}{ z=h=#puoQ~}(b3kG*tX1CFNfk%BSs-@pj#8&nsLk82z_TOsdk2lVmf}3S}46pLF-+` zSz9bLHX^lA$yU0x(~Y&790CebToR=e=QWbcJV~`(#2Rf9@;X2q`>7+ z>>@)Hy|F+f7LNAnQM|9~w$D>WIXk4}LAR3*C(UEp9isRWDanGh_R@ zW3`B@Z$+SN)xp&^Bj8?jbFELz*j60P52|W!o|r$8u4S?S8bbdW?WS%S7WBO z@$Ir(W$D_ja_!ccqbRFX@}Roz=EeDo>FRd5x_xnMxq8P;Nyb&3a&5@eZFsxsR#Uoe zyIi+@<`|l7Xh&_tNusw=XyBrH<8p z>fkoCELMH1>6=aImVz*E48+$iPAyS{oX$xHpBWmLz2Mux&AMs>Vp

P1Ix@-j(Cl5BV0xxZVHpGr?%#`> zUM;kF@;^cZ@1{C!xvv!43L)hq8r<+R(3OTznvt7-8IWabB$70;ejFfYNa!Ivwag97it?(26 z7ot~5-b_Nha8b@x1>jvbSMAR=x90*4xt?co-#(nLuA@T_+*NrRQn=7nb>vM5nMrM1 z-hvRCM0p!Rc7z-VIZ352??T8;{5$g%2vriNKkq@vO9I__A3{~6W-D;(~*Zq2^x9aI#7k8Dt;oFIiFz(CbN51Cd@gu*uSRU4TW zzV3NZsXG^J!Y!Ma0VfZNN4)g#K5T13R}gSH@Rg7NcMEJfcwf{`;0pv~1bFikLiovy z`t8An!t?Mv#-p6$O&B3l!-ohUJ=6y?VS`V|g#5yRyGuFb%JIOH0UtHMxQcrTK5D>0 zF7WA8d|vtLO#N)Z7*TydZbUaKA070l&vRBd;p#_R0*?>J{0oR;0e9I+)xV|OpV0lU zkgJTdWOKjD!UOypfPxZJn1m>=rzq+^+4&pd{){+3Bi7G|{WIeDH`4w&IVO=~pObx` zlWvK0-zTm2Nz;82xKEnzla2>9bs16xduOd(s?J&hk|mI--Mwn*xnf*5TB*86fy+9| Om+`g#2SGsD#{UD^NX-NQ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbb85b3c197d9a0553733352edf37d6699100ef2 GIT binary patch literal 18691 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∈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 Ox_xflKT(j7WB-2~@((=# literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8d4de4afe21747d778917408befe1875bc05796 GIT binary patch literal 27323 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 z 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5ad634bde5eba7715d44434af36b4cfbf8ff497 GIT binary patch literal 5317 zcmdT|-ESMm5#QsH#}84IMB1@rTlU&g?9jEPI&Bcdb%V%Xkx@rBYz1iF5o{Q?rUEX#BGGD3j|0XnwNs9RKyVa(3!mtQI=dg z2~ePvI6JqqJ3BKw``bDGv9~uv;ImjcQhn4<$QSq!eiA;TEVqEUMGRsnHDW0xML|AR ziM8YD*;3Z(E%jQtQcfW;@;WgR zH;JL$iTSIP`ea`c`cka#jvDm#H)F;0m-tXrydK@Q9ancv#}-px;>2RcQ{Oy)#>5pUN5sRIlRKkvrQL9_JxmC zIUV$b9SvUdwwZRN)-YI^`+Ca3sETi=Posd{fahC;k&(^B`9my1ssqJ_>aaX$&=(?`8$(5dP#9G_d?gSQIPo_ybd8Kzv z7II;r6qNUcy=!f^`~n$y>Zo~gP5l{}R~C~~o@Oz(>KKrzaa*^Tr$XlN0nlBH@j+;K z9{B`+4td#uLfOj;C`xhkilf;)izJ5xW8vkhx^2{={PW@*($q^-YK~xwDK9Qq?a~t! zd&wYey%Z19U!*`Q4L+;x@gp;D3dY6E1>KrC!NgV9sn1+64Mxv#=L)O1V&;@C+=+9i z>71aaYPz^OGpSduGEPr8SDcx#IcCqzoH1u-gz2(Fbr9*7nV6}%ZoT4gHeMD@yE1Nu zc__x}O}-0EXbnD|qGN%rTR|#*4dgwtvW4V#e=zsqS`Jtu4&{BV4Y0D1$-?nF^M=N9&lARpc zGWc0x-zSBAx92}Cj4z}*F*QBBl-=^1H{O5aS7$#5X6pT^2icuVecKlL9(7dGyA^x> zl-;>1aAWQd_D>uk_mAX{Wz`KQW&}@67BcZCMEO5FHNTDB0)&6s>1q0B1hOtYIfWii6E5Vu)!McpVSK?4aoR6{{GB|4lzHxdk}~=2 zznluv1(M^@!eo#p{}!0sHzu|ni!1lH$0x?g{apjdkLQmm>K_yp_)2i~!9W5xVgR)W z4&6{20@4j-QJGp)yuy^@p0?^WW&v_D<0R(}2YeZZY4Sh`)I_O_nG9-c_lAJ2$C3*$ zVHttvJ@PQO=hNKC$ElHJcqGNFM3mJYc|+1Rg+NQ(S{;B(7oNNOC6pngOFUbBK9sr5 zx@lh?I}I0*?mGN!+5Zkqo^os^VQ+O{?e-4(J&iNB!EKHgVqgX@qH+nF2asF8Apair z6_6Z6atKIk=<&tF%m1BXTn0;Gs~qdEl=4#LGGceR>}ASj%P|^&xn(a~F2CE*YraIz z^fL1Dyyn)-SuYpxmTm*;alRKVz=U2T`~~DG5~#0yvIQ7%56Df@$r3uc^vaKyUO4zw z-*95^^Zp&31hgHEBv6^fLm6Hw!0GpZsn+w81#*H4RbK<3hk%fvS4wXvQuT-Gb;5h|t654b#RqXW&#~s^- zdk0FosCcPF^#`o;3Ye7$+! za6^iU{%7zg6V@H)&(;`q9jaFZLxaj;EE9<)Y&tnTeQx~l*x_!)^Jc9^9UHXHGOEw%W=(1b3ru(YswNa& zo9KISgSK^wghOi%7^Dm7l7B(b({?ZC2mthh;F+;WAaI~* zgOCEiI2^E8ce^cnt7>9=HUw~%VPxRd2Rt-Il^JxNQA=;qaOJDEGmo+NJ(q!utb=ev z!PuyUOGmK;10>%ahcP<6esxiG8Z`q#yrvIsaPSf1;@cI){ve%gxYRN)S6vG0nHG$L z?cus97`-^f=GXEQuQQV~<6@*1_Z@p6ikKVt8i*cQs0eP#FfRnvIc&GRme-DRiqkY>90M+NcMXc1IA*4rxhLeWLRDz<>brPnla5|X@3CF3$bcnbN z+QtOL6C`sQ^i?5pbzKMuJ`9YkW7p~?Z&KKG2B#7h7W67(^+S5ioMQ(mB%9O?FDSTU zs6+rJ;8{3%_SDmMpa;g6DB0M@vd8c=9M zjmuT&;)j8FnqM}1Ni2X_P4EPYG$gXXmyY4D60&IaU+Lt}6?ky`-z>03l*T<~wU;{T z7uB!wGcW|}2Z0q*M^zN%uVn8RWZ*BP_v?6%68oA&Wan#&GWr!kx^gP^qB8pN_5=SS I&`M|i199R59RL6T literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35dd59671181aa910c626f071bab1c8f43193048 GIT binary patch literal 15638 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_sync/connection.py b/.venv/Lib/site-packages/httpcore/_sync/connection.py new file mode 100644 index 0000000..363f8be --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_sync/connection.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import itertools +import logging +import ssl +import types +import typing + +from .._backends.sync import SyncBackend +from .._backends.base import SOCKET_OPTION, NetworkBackend, NetworkStream +from .._exceptions import ConnectError, ConnectTimeout +from .._models import Origin, Request, Response +from .._ssl import default_ssl_context +from .._synchronization import Lock +from .._trace import Trace +from .http11 import HTTP11Connection +from .interfaces import ConnectionInterface + +RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc. + + +logger = logging.getLogger("httpcore.connection") + + +def exponential_backoff(factor: float) -> typing.Iterator[float]: + """ + Generate a geometric sequence that has a ratio of 2 and starts with 0. + + For example: + - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...` + - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...` + """ + yield 0 + for n in itertools.count(): + yield factor * 2**n + + +class HTTPConnection(ConnectionInterface): + def __init__( + self, + origin: Origin, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + self._origin = origin + self._ssl_context = ssl_context + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._retries = retries + self._local_address = local_address + self._uds = uds + + self._network_backend: NetworkBackend = ( + SyncBackend() if network_backend is None else network_backend + ) + self._connection: ConnectionInterface | None = None + self._connect_failed: bool = False + self._request_lock = Lock() + self._socket_options = socket_options + + def handle_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection to {self._origin}" + ) + + try: + with self._request_lock: + if self._connection is None: + stream = self._connect(request) + + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + if http2_negotiated or (self._http2 and not self._http1): + from .http2 import HTTP2Connection + + self._connection = HTTP2Connection( + origin=self._origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = HTTP11Connection( + origin=self._origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + except BaseException as exc: + self._connect_failed = True + raise exc + + return self._connection.handle_request(request) + + def _connect(self, request: Request) -> NetworkStream: + timeouts = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname", None) + timeout = timeouts.get("connect", None) + + retries_left = self._retries + delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR) + + while True: + try: + if self._uds is None: + kwargs = { + "host": self._origin.host.decode("ascii"), + "port": self._origin.port, + "local_address": self._local_address, + "timeout": timeout, + "socket_options": self._socket_options, + } + with Trace("connect_tcp", logger, request, kwargs) as trace: + stream = self._network_backend.connect_tcp(**kwargs) + trace.return_value = stream + else: + kwargs = { + "path": self._uds, + "timeout": timeout, + "socket_options": self._socket_options, + } + with Trace( + "connect_unix_socket", logger, request, kwargs + ) as trace: + stream = self._network_backend.connect_unix_socket( + **kwargs + ) + trace.return_value = stream + + if self._origin.scheme in (b"https", b"wss"): + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": sni_hostname + or self._origin.host.decode("ascii"), + "timeout": timeout, + } + with Trace("start_tls", logger, request, kwargs) as trace: + stream = stream.start_tls(**kwargs) + trace.return_value = stream + return stream + except (ConnectError, ConnectTimeout): + if retries_left <= 0: + raise + retries_left -= 1 + delay = next(delays) + with Trace("retry", logger, request, kwargs) as trace: + self._network_backend.sleep(delay) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def close(self) -> None: + if self._connection is not None: + with Trace("close", logger, None, {}): + self._connection.close() + + def is_available(self) -> bool: + if self._connection is None: + # If HTTP/2 support is enabled, and the resulting connection could + # end up as HTTP/2 then we should indicate the connection as being + # available to service multiple requests. + return ( + self._http2 + and (self._origin.scheme == b"https" or not self._http1) + and not self._connect_failed + ) + return self._connection.is_available() + + def has_expired(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.has_expired() + + def is_idle(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.is_idle() + + def is_closed(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.is_closed() + + def info(self) -> str: + if self._connection is None: + return "CONNECTION FAILED" if self._connect_failed else "CONNECTING" + return self._connection.info() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + def __enter__(self) -> HTTPConnection: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self.close() diff --git a/.venv/Lib/site-packages/httpcore/_sync/connection_pool.py b/.venv/Lib/site-packages/httpcore/_sync/connection_pool.py new file mode 100644 index 0000000..9ccfa53 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_sync/connection_pool.py @@ -0,0 +1,420 @@ +from __future__ import annotations + +import ssl +import sys +import types +import typing + +from .._backends.sync import SyncBackend +from .._backends.base import SOCKET_OPTION, NetworkBackend +from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol +from .._models import Origin, Proxy, Request, Response +from .._synchronization import Event, ShieldCancellation, ThreadLock +from .connection import HTTPConnection +from .interfaces import ConnectionInterface, RequestInterface + + +class PoolRequest: + def __init__(self, request: Request) -> None: + self.request = request + self.connection: ConnectionInterface | None = None + self._connection_acquired = Event() + + def assign_to_connection(self, connection: ConnectionInterface | None) -> None: + self.connection = connection + self._connection_acquired.set() + + def clear_connection(self) -> None: + self.connection = None + self._connection_acquired = Event() + + def wait_for_connection( + self, timeout: float | None = None + ) -> ConnectionInterface: + if self.connection is None: + self._connection_acquired.wait(timeout=timeout) + assert self.connection is not None + return self.connection + + def is_queued(self) -> bool: + return self.connection is None + + +class ConnectionPool(RequestInterface): + """ + A connection pool for making HTTP requests. + """ + + def __init__( + self, + ssl_context: ssl.SSLContext | None = None, + proxy: Proxy | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish a + connection. + local_address: Local address to connect from. Can also be used to connect + using a particular address family. Using `local_address="0.0.0.0"` + will connect using an `AF_INET` address (IPv4), while using + `local_address="::"` will connect using an `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + socket_options: Socket options that have to be included + in the TCP socket when the connection was established. + """ + self._ssl_context = ssl_context + self._proxy = proxy + self._max_connections = ( + sys.maxsize if max_connections is None else max_connections + ) + self._max_keepalive_connections = ( + sys.maxsize + if max_keepalive_connections is None + else max_keepalive_connections + ) + self._max_keepalive_connections = min( + self._max_connections, self._max_keepalive_connections + ) + + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._retries = retries + self._local_address = local_address + self._uds = uds + + self._network_backend = ( + SyncBackend() if network_backend is None else network_backend + ) + self._socket_options = socket_options + + # The mutable state on a connection pool is the queue of incoming requests, + # and the set of connections that are servicing those requests. + self._connections: list[ConnectionInterface] = [] + self._requests: list[PoolRequest] = [] + + # We only mutate the state of the connection pool within an 'optional_thread_lock' + # context. This holds a threading lock unless we're running in async mode, + # in which case it is a no-op. + self._optional_thread_lock = ThreadLock() + + def create_connection(self, origin: Origin) -> ConnectionInterface: + if self._proxy is not None: + if self._proxy.url.scheme in (b"socks5", b"socks5h"): + from .socks_proxy import Socks5Connection + + return Socks5Connection( + proxy_origin=self._proxy.url.origin, + proxy_auth=self._proxy.auth, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + elif origin.scheme == b"http": + from .http_proxy import ForwardHTTPConnection + + return ForwardHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + ) + from .http_proxy import TunnelHTTPConnection + + return TunnelHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + return HTTPConnection( + origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + retries=self._retries, + local_address=self._local_address, + uds=self._uds, + network_backend=self._network_backend, + socket_options=self._socket_options, + ) + + @property + def connections(self) -> list[ConnectionInterface]: + """ + Return a list of the connections currently in the pool. + + For example: + + ```python + >>> pool.connections + [ + , + , + , + ] + ``` + """ + return list(self._connections) + + def handle_request(self, request: Request) -> Response: + """ + Send an HTTP request, and return an HTTP response. + + This is the core implementation that is called into by `.request()` or `.stream()`. + """ + scheme = request.url.scheme.decode() + if scheme == "": + raise UnsupportedProtocol( + "Request URL is missing an 'http://' or 'https://' protocol." + ) + if scheme not in ("http", "https", "ws", "wss"): + raise UnsupportedProtocol( + f"Request URL has an unsupported protocol '{scheme}://'." + ) + + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("pool", None) + + with self._optional_thread_lock: + # Add the incoming request to our request queue. + pool_request = PoolRequest(request) + self._requests.append(pool_request) + + try: + while True: + with self._optional_thread_lock: + # Assign incoming requests to available connections, + # closing or creating new connections as required. + closing = self._assign_requests_to_connections() + self._close_connections(closing) + + # Wait until this request has an assigned connection. + connection = pool_request.wait_for_connection(timeout=timeout) + + try: + # Send the request on the assigned connection. + response = connection.handle_request( + pool_request.request + ) + except ConnectionNotAvailable: + # In some cases a connection may initially be available to + # handle a request, but then become unavailable. + # + # In this case we clear the connection and try again. + pool_request.clear_connection() + else: + break # pragma: nocover + + except BaseException as exc: + with self._optional_thread_lock: + # For any exception or cancellation we remove the request from + # the queue, and then re-assign requests to connections. + self._requests.remove(pool_request) + closing = self._assign_requests_to_connections() + + self._close_connections(closing) + raise exc from None + + # Return the response. Note that in this case we still have to manage + # the point at which the response is closed. + assert isinstance(response.stream, typing.Iterable) + return Response( + status=response.status, + headers=response.headers, + content=PoolByteStream( + stream=response.stream, pool_request=pool_request, pool=self + ), + extensions=response.extensions, + ) + + def _assign_requests_to_connections(self) -> list[ConnectionInterface]: + """ + Manage the state of the connection pool, assigning incoming + requests to connections as available. + + Called whenever a new request is added or removed from the pool. + + Any closing connections are returned, allowing the I/O for closing + those connections to be handled seperately. + """ + closing_connections = [] + + # First we handle cleaning up any connections that are closed, + # have expired their keep-alive, or surplus idle connections. + for connection in list(self._connections): + if connection.is_closed(): + # log: "removing closed connection" + self._connections.remove(connection) + elif connection.has_expired(): + # log: "closing expired connection" + self._connections.remove(connection) + closing_connections.append(connection) + elif ( + connection.is_idle() + and len([connection.is_idle() for connection in self._connections]) + > self._max_keepalive_connections + ): + # log: "closing idle connection" + self._connections.remove(connection) + closing_connections.append(connection) + + # Assign queued requests to connections. + queued_requests = [request for request in self._requests if request.is_queued()] + for pool_request in queued_requests: + origin = pool_request.request.url.origin + available_connections = [ + connection + for connection in self._connections + if connection.can_handle_request(origin) and connection.is_available() + ] + idle_connections = [ + connection for connection in self._connections if connection.is_idle() + ] + + # There are three cases for how we may be able to handle the request: + # + # 1. There is an existing connection that can handle the request. + # 2. We can create a new connection to handle the request. + # 3. We can close an idle connection and then create a new connection + # to handle the request. + if available_connections: + # log: "reusing existing connection" + connection = available_connections[0] + pool_request.assign_to_connection(connection) + elif len(self._connections) < self._max_connections: + # log: "creating new connection" + connection = self.create_connection(origin) + self._connections.append(connection) + pool_request.assign_to_connection(connection) + elif idle_connections: + # log: "closing idle connection" + connection = idle_connections[0] + self._connections.remove(connection) + closing_connections.append(connection) + # log: "creating new connection" + connection = self.create_connection(origin) + self._connections.append(connection) + pool_request.assign_to_connection(connection) + + return closing_connections + + def _close_connections(self, closing: list[ConnectionInterface]) -> None: + # Close connections which have been removed from the pool. + with ShieldCancellation(): + for connection in closing: + connection.close() + + def close(self) -> None: + # Explicitly close the connection pool. + # Clears all existing requests and connections. + with self._optional_thread_lock: + closing_connections = list(self._connections) + self._connections = [] + self._close_connections(closing_connections) + + def __enter__(self) -> ConnectionPool: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self.close() + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + with self._optional_thread_lock: + request_is_queued = [request.is_queued() for request in self._requests] + connection_is_idle = [ + connection.is_idle() for connection in self._connections + ] + + num_active_requests = request_is_queued.count(False) + num_queued_requests = request_is_queued.count(True) + num_active_connections = connection_is_idle.count(False) + num_idle_connections = connection_is_idle.count(True) + + requests_info = ( + f"Requests: {num_active_requests} active, {num_queued_requests} queued" + ) + connection_info = ( + f"Connections: {num_active_connections} active, {num_idle_connections} idle" + ) + + return f"<{class_name} [{requests_info} | {connection_info}]>" + + +class PoolByteStream: + def __init__( + self, + stream: typing.Iterable[bytes], + pool_request: PoolRequest, + pool: ConnectionPool, + ) -> None: + self._stream = stream + self._pool_request = pool_request + self._pool = pool + self._closed = False + + def __iter__(self) -> typing.Iterator[bytes]: + try: + for part in self._stream: + yield part + except BaseException as exc: + self.close() + raise exc from None + + def close(self) -> None: + if not self._closed: + self._closed = True + with ShieldCancellation(): + if hasattr(self._stream, "close"): + self._stream.close() + + with self._pool._optional_thread_lock: + self._pool._requests.remove(self._pool_request) + closing = self._pool._assign_requests_to_connections() + + self._pool._close_connections(closing) diff --git a/.venv/Lib/site-packages/httpcore/_sync/http11.py b/.venv/Lib/site-packages/httpcore/_sync/http11.py new file mode 100644 index 0000000..ebd3a97 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_sync/http11.py @@ -0,0 +1,379 @@ +from __future__ import annotations + +import enum +import logging +import ssl +import time +import types +import typing + +import h11 + +from .._backends.base import NetworkStream +from .._exceptions import ( + ConnectionNotAvailable, + LocalProtocolError, + RemoteProtocolError, + WriteError, + map_exceptions, +) +from .._models import Origin, Request, Response +from .._synchronization import Lock, ShieldCancellation +from .._trace import Trace +from .interfaces import ConnectionInterface + +logger = logging.getLogger("httpcore.http11") + + +# A subset of `h11.Event` types supported by `_send_event` +H11SendEvent = typing.Union[ + h11.Request, + h11.Data, + h11.EndOfMessage, +] + + +class HTTPConnectionState(enum.IntEnum): + NEW = 0 + ACTIVE = 1 + IDLE = 2 + CLOSED = 3 + + +class HTTP11Connection(ConnectionInterface): + READ_NUM_BYTES = 64 * 1024 + MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024 + + def __init__( + self, + origin: Origin, + stream: NetworkStream, + keepalive_expiry: float | None = None, + ) -> None: + self._origin = origin + self._network_stream = stream + self._keepalive_expiry: float | None = keepalive_expiry + self._expire_at: float | None = None + self._state = HTTPConnectionState.NEW + self._state_lock = Lock() + self._request_count = 0 + self._h11_state = h11.Connection( + our_role=h11.CLIENT, + max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE, + ) + + def handle_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection " + f"to {self._origin}" + ) + + with self._state_lock: + if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE): + self._request_count += 1 + self._state = HTTPConnectionState.ACTIVE + self._expire_at = None + else: + raise ConnectionNotAvailable() + + try: + kwargs = {"request": request} + try: + with Trace( + "send_request_headers", logger, request, kwargs + ) as trace: + self._send_request_headers(**kwargs) + with Trace("send_request_body", logger, request, kwargs) as trace: + self._send_request_body(**kwargs) + except WriteError: + # If we get a write error while we're writing the request, + # then we supress this error and move on to attempting to + # read the response. Servers can sometimes close the request + # pre-emptively and then respond with a well formed HTTP + # error response. + pass + + with Trace( + "receive_response_headers", logger, request, kwargs + ) as trace: + ( + http_version, + status, + reason_phrase, + headers, + trailing_data, + ) = self._receive_response_headers(**kwargs) + trace.return_value = ( + http_version, + status, + reason_phrase, + headers, + ) + + network_stream = self._network_stream + + # CONNECT or Upgrade request + if (status == 101) or ( + (request.method == b"CONNECT") and (200 <= status < 300) + ): + network_stream = HTTP11UpgradeStream(network_stream, trailing_data) + + return Response( + status=status, + headers=headers, + content=HTTP11ConnectionByteStream(self, request), + extensions={ + "http_version": http_version, + "reason_phrase": reason_phrase, + "network_stream": network_stream, + }, + ) + except BaseException as exc: + with ShieldCancellation(): + with Trace("response_closed", logger, request) as trace: + self._response_closed() + raise exc + + # Sending the request... + + def _send_request_headers(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + with map_exceptions({h11.LocalProtocolError: LocalProtocolError}): + event = h11.Request( + method=request.method, + target=request.url.target, + headers=request.headers, + ) + self._send_event(event, timeout=timeout) + + def _send_request_body(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + assert isinstance(request.stream, typing.Iterable) + for chunk in request.stream: + event = h11.Data(data=chunk) + self._send_event(event, timeout=timeout) + + self._send_event(h11.EndOfMessage(), timeout=timeout) + + def _send_event(self, event: h11.Event, timeout: float | None = None) -> None: + bytes_to_send = self._h11_state.send(event) + if bytes_to_send is not None: + self._network_stream.write(bytes_to_send, timeout=timeout) + + # Receiving the response... + + def _receive_response_headers( + self, request: Request + ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + while True: + event = self._receive_event(timeout=timeout) + if isinstance(event, h11.Response): + break + if ( + isinstance(event, h11.InformationalResponse) + and event.status_code == 101 + ): + break + + http_version = b"HTTP/" + event.http_version + + # h11 version 0.11+ supports a `raw_items` interface to get the + # raw header casing, rather than the enforced lowercase headers. + headers = event.headers.raw_items() + + trailing_data, _ = self._h11_state.trailing_data + + return http_version, event.status_code, event.reason, headers, trailing_data + + def _receive_response_body( + self, request: Request + ) -> typing.Iterator[bytes]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + while True: + event = self._receive_event(timeout=timeout) + if isinstance(event, h11.Data): + yield bytes(event.data) + elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)): + break + + def _receive_event( + self, timeout: float | None = None + ) -> h11.Event | type[h11.PAUSED]: + while True: + with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}): + event = self._h11_state.next_event() + + if event is h11.NEED_DATA: + data = self._network_stream.read( + self.READ_NUM_BYTES, timeout=timeout + ) + + # If we feed this case through h11 we'll raise an exception like: + # + # httpcore.RemoteProtocolError: can't handle event type + # ConnectionClosed when role=SERVER and state=SEND_RESPONSE + # + # Which is accurate, but not very informative from an end-user + # perspective. Instead we handle this case distinctly and treat + # it as a ConnectError. + if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE: + msg = "Server disconnected without sending a response." + raise RemoteProtocolError(msg) + + self._h11_state.receive_data(data) + else: + # mypy fails to narrow the type in the above if statement above + return event # type: ignore[return-value] + + def _response_closed(self) -> None: + with self._state_lock: + if ( + self._h11_state.our_state is h11.DONE + and self._h11_state.their_state is h11.DONE + ): + self._state = HTTPConnectionState.IDLE + self._h11_state.start_next_cycle() + if self._keepalive_expiry is not None: + now = time.monotonic() + self._expire_at = now + self._keepalive_expiry + else: + self.close() + + # Once the connection is no longer required... + + def close(self) -> None: + # Note that this method unilaterally closes the connection, and does + # not have any kind of locking in place around it. + self._state = HTTPConnectionState.CLOSED + self._network_stream.close() + + # The ConnectionInterface methods provide information about the state of + # the connection, allowing for a connection pooling implementation to + # determine when to reuse and when to close the connection... + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def is_available(self) -> bool: + # Note that HTTP/1.1 connections in the "NEW" state are not treated as + # being "available". The control flow which created the connection will + # be able to send an outgoing request, but the connection will not be + # acquired from the connection pool for any other request. + return self._state == HTTPConnectionState.IDLE + + def has_expired(self) -> bool: + now = time.monotonic() + keepalive_expired = self._expire_at is not None and now > self._expire_at + + # If the HTTP connection is idle but the socket is readable, then the + # only valid state is that the socket is about to return b"", indicating + # a server-initiated disconnect. + server_disconnected = ( + self._state == HTTPConnectionState.IDLE + and self._network_stream.get_extra_info("is_readable") + ) + + return keepalive_expired or server_disconnected + + def is_idle(self) -> bool: + return self._state == HTTPConnectionState.IDLE + + def is_closed(self) -> bool: + return self._state == HTTPConnectionState.CLOSED + + def info(self) -> str: + origin = str(self._origin) + return ( + f"{origin!r}, HTTP/1.1, {self._state.name}, " + f"Request Count: {self._request_count}" + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + origin = str(self._origin) + return ( + f"<{class_name} [{origin!r}, {self._state.name}, " + f"Request Count: {self._request_count}]>" + ) + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + def __enter__(self) -> HTTP11Connection: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self.close() + + +class HTTP11ConnectionByteStream: + def __init__(self, connection: HTTP11Connection, request: Request) -> None: + self._connection = connection + self._request = request + self._closed = False + + def __iter__(self) -> typing.Iterator[bytes]: + kwargs = {"request": self._request} + try: + with Trace("receive_response_body", logger, self._request, kwargs): + for chunk in self._connection._receive_response_body(**kwargs): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + with ShieldCancellation(): + self.close() + raise exc + + def close(self) -> None: + if not self._closed: + self._closed = True + with Trace("response_closed", logger, self._request): + self._connection._response_closed() + + +class HTTP11UpgradeStream(NetworkStream): + def __init__(self, stream: NetworkStream, leading_data: bytes) -> None: + self._stream = stream + self._leading_data = leading_data + + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + if self._leading_data: + buffer = self._leading_data[:max_bytes] + self._leading_data = self._leading_data[max_bytes:] + return buffer + else: + return self._stream.read(max_bytes, timeout) + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + self._stream.write(buffer, timeout) + + def close(self) -> None: + self._stream.close() + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + return self._stream.start_tls(ssl_context, server_hostname, timeout) + + def get_extra_info(self, info: str) -> typing.Any: + return self._stream.get_extra_info(info) diff --git a/.venv/Lib/site-packages/httpcore/_sync/http2.py b/.venv/Lib/site-packages/httpcore/_sync/http2.py new file mode 100644 index 0000000..ddcc189 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_sync/http2.py @@ -0,0 +1,592 @@ +from __future__ import annotations + +import enum +import logging +import time +import types +import typing + +import h2.config +import h2.connection +import h2.events +import h2.exceptions +import h2.settings + +from .._backends.base import NetworkStream +from .._exceptions import ( + ConnectionNotAvailable, + LocalProtocolError, + RemoteProtocolError, +) +from .._models import Origin, Request, Response +from .._synchronization import Lock, Semaphore, ShieldCancellation +from .._trace import Trace +from .interfaces import ConnectionInterface + +logger = logging.getLogger("httpcore.http2") + + +def has_body_headers(request: Request) -> bool: + return any( + k.lower() == b"content-length" or k.lower() == b"transfer-encoding" + for k, v in request.headers + ) + + +class HTTPConnectionState(enum.IntEnum): + ACTIVE = 1 + IDLE = 2 + CLOSED = 3 + + +class HTTP2Connection(ConnectionInterface): + READ_NUM_BYTES = 64 * 1024 + CONFIG = h2.config.H2Configuration(validate_inbound_headers=False) + + def __init__( + self, + origin: Origin, + stream: NetworkStream, + keepalive_expiry: float | None = None, + ): + self._origin = origin + self._network_stream = stream + self._keepalive_expiry: float | None = keepalive_expiry + self._h2_state = h2.connection.H2Connection(config=self.CONFIG) + self._state = HTTPConnectionState.IDLE + self._expire_at: float | None = None + self._request_count = 0 + self._init_lock = Lock() + self._state_lock = Lock() + self._read_lock = Lock() + self._write_lock = Lock() + self._sent_connection_init = False + self._used_all_stream_ids = False + self._connection_error = False + + # Mapping from stream ID to response stream events. + self._events: dict[ + int, + list[ + h2.events.ResponseReceived + | h2.events.DataReceived + | h2.events.StreamEnded + | h2.events.StreamReset, + ], + ] = {} + + # Connection terminated events are stored as state since + # we need to handle them for all streams. + self._connection_terminated: h2.events.ConnectionTerminated | None = None + + self._read_exception: Exception | None = None + self._write_exception: Exception | None = None + + def handle_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + # This cannot occur in normal operation, since the connection pool + # will only send requests on connections that handle them. + # It's in place simply for resilience as a guard against incorrect + # usage, for anyone working directly with httpcore connections. + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection " + f"to {self._origin}" + ) + + with self._state_lock: + if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE): + self._request_count += 1 + self._expire_at = None + self._state = HTTPConnectionState.ACTIVE + else: + raise ConnectionNotAvailable() + + with self._init_lock: + if not self._sent_connection_init: + try: + sci_kwargs = {"request": request} + with Trace( + "send_connection_init", logger, request, sci_kwargs + ): + self._send_connection_init(**sci_kwargs) + except BaseException as exc: + with ShieldCancellation(): + self.close() + raise exc + + self._sent_connection_init = True + + # Initially start with just 1 until the remote server provides + # its max_concurrent_streams value + self._max_streams = 1 + + local_settings_max_streams = ( + self._h2_state.local_settings.max_concurrent_streams + ) + self._max_streams_semaphore = Semaphore(local_settings_max_streams) + + for _ in range(local_settings_max_streams - self._max_streams): + self._max_streams_semaphore.acquire() + + self._max_streams_semaphore.acquire() + + try: + stream_id = self._h2_state.get_next_available_stream_id() + self._events[stream_id] = [] + except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover + self._used_all_stream_ids = True + self._request_count -= 1 + raise ConnectionNotAvailable() + + try: + kwargs = {"request": request, "stream_id": stream_id} + with Trace("send_request_headers", logger, request, kwargs): + self._send_request_headers(request=request, stream_id=stream_id) + with Trace("send_request_body", logger, request, kwargs): + self._send_request_body(request=request, stream_id=stream_id) + with Trace( + "receive_response_headers", logger, request, kwargs + ) as trace: + status, headers = self._receive_response( + request=request, stream_id=stream_id + ) + trace.return_value = (status, headers) + + return Response( + status=status, + headers=headers, + content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id), + extensions={ + "http_version": b"HTTP/2", + "network_stream": self._network_stream, + "stream_id": stream_id, + }, + ) + except BaseException as exc: # noqa: PIE786 + with ShieldCancellation(): + kwargs = {"stream_id": stream_id} + with Trace("response_closed", logger, request, kwargs): + self._response_closed(stream_id=stream_id) + + if isinstance(exc, h2.exceptions.ProtocolError): + # One case where h2 can raise a protocol error is when a + # closed frame has been seen by the state machine. + # + # This happens when one stream is reading, and encounters + # a GOAWAY event. Other flows of control may then raise + # a protocol error at any point they interact with the 'h2_state'. + # + # In this case we'll have stored the event, and should raise + # it as a RemoteProtocolError. + if self._connection_terminated: # pragma: nocover + raise RemoteProtocolError(self._connection_terminated) + # If h2 raises a protocol error in some other state then we + # must somehow have made a protocol violation. + raise LocalProtocolError(exc) # pragma: nocover + + raise exc + + def _send_connection_init(self, request: Request) -> None: + """ + The HTTP/2 connection requires some initial setup before we can start + using individual request/response streams on it. + """ + # Need to set these manually here instead of manipulating via + # __setitem__() otherwise the H2Connection will emit SettingsUpdate + # frames in addition to sending the undesired defaults. + self._h2_state.local_settings = h2.settings.Settings( + client=True, + initial_values={ + # Disable PUSH_PROMISE frames from the server since we don't do anything + # with them for now. Maybe when we support caching? + h2.settings.SettingCodes.ENABLE_PUSH: 0, + # These two are taken from h2 for safe defaults + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100, + h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536, + }, + ) + + # Some websites (*cough* Yahoo *cough*) balk at this setting being + # present in the initial handshake since it's not defined in the original + # RFC despite the RFC mandating ignoring settings you don't know about. + del self._h2_state.local_settings[ + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL + ] + + self._h2_state.initiate_connection() + self._h2_state.increment_flow_control_window(2**24) + self._write_outgoing_data(request) + + # Sending the request... + + def _send_request_headers(self, request: Request, stream_id: int) -> None: + """ + Send the request headers to a given stream ID. + """ + end_stream = not has_body_headers(request) + + # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'. + # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require + # HTTP/1.1 style headers, and map them appropriately if we end up on + # an HTTP/2 connection. + authority = [v for k, v in request.headers if k.lower() == b"host"][0] + + headers = [ + (b":method", request.method), + (b":authority", authority), + (b":scheme", request.url.scheme), + (b":path", request.url.target), + ] + [ + (k.lower(), v) + for k, v in request.headers + if k.lower() + not in ( + b"host", + b"transfer-encoding", + ) + ] + + self._h2_state.send_headers(stream_id, headers, end_stream=end_stream) + self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id) + self._write_outgoing_data(request) + + def _send_request_body(self, request: Request, stream_id: int) -> None: + """ + Iterate over the request body sending it to a given stream ID. + """ + if not has_body_headers(request): + return + + assert isinstance(request.stream, typing.Iterable) + for data in request.stream: + self._send_stream_data(request, stream_id, data) + self._send_end_stream(request, stream_id) + + def _send_stream_data( + self, request: Request, stream_id: int, data: bytes + ) -> None: + """ + Send a single chunk of data in one or more data frames. + """ + while data: + max_flow = self._wait_for_outgoing_flow(request, stream_id) + chunk_size = min(len(data), max_flow) + chunk, data = data[:chunk_size], data[chunk_size:] + self._h2_state.send_data(stream_id, chunk) + self._write_outgoing_data(request) + + def _send_end_stream(self, request: Request, stream_id: int) -> None: + """ + Send an empty data frame on on a given stream ID with the END_STREAM flag set. + """ + self._h2_state.end_stream(stream_id) + self._write_outgoing_data(request) + + # Receiving the response... + + def _receive_response( + self, request: Request, stream_id: int + ) -> tuple[int, list[tuple[bytes, bytes]]]: + """ + Return the response status code and headers for a given stream ID. + """ + while True: + event = self._receive_stream_event(request, stream_id) + if isinstance(event, h2.events.ResponseReceived): + break + + status_code = 200 + headers = [] + assert event.headers is not None + for k, v in event.headers: + if k == b":status": + status_code = int(v.decode("ascii", errors="ignore")) + elif not k.startswith(b":"): + headers.append((k, v)) + + return (status_code, headers) + + def _receive_response_body( + self, request: Request, stream_id: int + ) -> typing.Iterator[bytes]: + """ + Iterator that returns the bytes of the response body for a given stream ID. + """ + while True: + event = self._receive_stream_event(request, stream_id) + if isinstance(event, h2.events.DataReceived): + assert event.flow_controlled_length is not None + assert event.data is not None + amount = event.flow_controlled_length + self._h2_state.acknowledge_received_data(amount, stream_id) + self._write_outgoing_data(request) + yield event.data + elif isinstance(event, h2.events.StreamEnded): + break + + def _receive_stream_event( + self, request: Request, stream_id: int + ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded: + """ + Return the next available event for a given stream ID. + + Will read more data from the network if required. + """ + while not self._events.get(stream_id): + self._receive_events(request, stream_id) + event = self._events[stream_id].pop(0) + if isinstance(event, h2.events.StreamReset): + raise RemoteProtocolError(event) + return event + + def _receive_events( + self, request: Request, stream_id: int | None = None + ) -> None: + """ + Read some data from the network until we see one or more events + for a given stream ID. + """ + with self._read_lock: + if self._connection_terminated is not None: + last_stream_id = self._connection_terminated.last_stream_id + if stream_id and last_stream_id and stream_id > last_stream_id: + self._request_count -= 1 + raise ConnectionNotAvailable() + raise RemoteProtocolError(self._connection_terminated) + + # This conditional is a bit icky. We don't want to block reading if we've + # actually got an event to return for a given stream. We need to do that + # check *within* the atomic read lock. Though it also need to be optional, + # because when we call it from `_wait_for_outgoing_flow` we *do* want to + # block until we've available flow control, event when we have events + # pending for the stream ID we're attempting to send on. + if stream_id is None or not self._events.get(stream_id): + events = self._read_incoming_data(request) + for event in events: + if isinstance(event, h2.events.RemoteSettingsChanged): + with Trace( + "receive_remote_settings", logger, request + ) as trace: + self._receive_remote_settings_change(event) + trace.return_value = event + + elif isinstance( + event, + ( + h2.events.ResponseReceived, + h2.events.DataReceived, + h2.events.StreamEnded, + h2.events.StreamReset, + ), + ): + if event.stream_id in self._events: + self._events[event.stream_id].append(event) + + elif isinstance(event, h2.events.ConnectionTerminated): + self._connection_terminated = event + + self._write_outgoing_data(request) + + def _receive_remote_settings_change( + self, event: h2.events.RemoteSettingsChanged + ) -> None: + max_concurrent_streams = event.changed_settings.get( + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS + ) + if max_concurrent_streams: + new_max_streams = min( + max_concurrent_streams.new_value, + self._h2_state.local_settings.max_concurrent_streams, + ) + if new_max_streams and new_max_streams != self._max_streams: + while new_max_streams > self._max_streams: + self._max_streams_semaphore.release() + self._max_streams += 1 + while new_max_streams < self._max_streams: + self._max_streams_semaphore.acquire() + self._max_streams -= 1 + + def _response_closed(self, stream_id: int) -> None: + self._max_streams_semaphore.release() + del self._events[stream_id] + with self._state_lock: + if self._connection_terminated and not self._events: + self.close() + + elif self._state == HTTPConnectionState.ACTIVE and not self._events: + self._state = HTTPConnectionState.IDLE + if self._keepalive_expiry is not None: + now = time.monotonic() + self._expire_at = now + self._keepalive_expiry + if self._used_all_stream_ids: # pragma: nocover + self.close() + + def close(self) -> None: + # Note that this method unilaterally closes the connection, and does + # not have any kind of locking in place around it. + self._h2_state.close_connection() + self._state = HTTPConnectionState.CLOSED + self._network_stream.close() + + # Wrappers around network read/write operations... + + def _read_incoming_data(self, request: Request) -> list[h2.events.Event]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + if self._read_exception is not None: + raise self._read_exception # pragma: nocover + + try: + data = self._network_stream.read(self.READ_NUM_BYTES, timeout) + if data == b"": + raise RemoteProtocolError("Server disconnected") + except Exception as exc: + # If we get a network error we should: + # + # 1. Save the exception and just raise it immediately on any future reads. + # (For example, this means that a single read timeout or disconnect will + # immediately close all pending streams. Without requiring multiple + # sequential timeouts.) + # 2. Mark the connection as errored, so that we don't accept any other + # incoming requests. + self._read_exception = exc + self._connection_error = True + raise exc + + events: list[h2.events.Event] = self._h2_state.receive_data(data) + + return events + + def _write_outgoing_data(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + with self._write_lock: + data_to_send = self._h2_state.data_to_send() + + if self._write_exception is not None: + raise self._write_exception # pragma: nocover + + try: + self._network_stream.write(data_to_send, timeout) + except Exception as exc: # pragma: nocover + # If we get a network error we should: + # + # 1. Save the exception and just raise it immediately on any future write. + # (For example, this means that a single write timeout or disconnect will + # immediately close all pending streams. Without requiring multiple + # sequential timeouts.) + # 2. Mark the connection as errored, so that we don't accept any other + # incoming requests. + self._write_exception = exc + self._connection_error = True + raise exc + + # Flow control... + + def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int: + """ + Returns the maximum allowable outgoing flow for a given stream. + + If the allowable flow is zero, then waits on the network until + WindowUpdated frames have increased the flow rate. + https://tools.ietf.org/html/rfc7540#section-6.9 + """ + local_flow: int = self._h2_state.local_flow_control_window(stream_id) + max_frame_size: int = self._h2_state.max_outbound_frame_size + flow = min(local_flow, max_frame_size) + while flow == 0: + self._receive_events(request) + local_flow = self._h2_state.local_flow_control_window(stream_id) + max_frame_size = self._h2_state.max_outbound_frame_size + flow = min(local_flow, max_frame_size) + return flow + + # Interface for connection pooling... + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def is_available(self) -> bool: + return ( + self._state != HTTPConnectionState.CLOSED + and not self._connection_error + and not self._used_all_stream_ids + and not ( + self._h2_state.state_machine.state + == h2.connection.ConnectionState.CLOSED + ) + ) + + def has_expired(self) -> bool: + now = time.monotonic() + return self._expire_at is not None and now > self._expire_at + + def is_idle(self) -> bool: + return self._state == HTTPConnectionState.IDLE + + def is_closed(self) -> bool: + return self._state == HTTPConnectionState.CLOSED + + def info(self) -> str: + origin = str(self._origin) + return ( + f"{origin!r}, HTTP/2, {self._state.name}, " + f"Request Count: {self._request_count}" + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + origin = str(self._origin) + return ( + f"<{class_name} [{origin!r}, {self._state.name}, " + f"Request Count: {self._request_count}]>" + ) + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + def __enter__(self) -> HTTP2Connection: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self.close() + + +class HTTP2ConnectionByteStream: + def __init__( + self, connection: HTTP2Connection, request: Request, stream_id: int + ) -> None: + self._connection = connection + self._request = request + self._stream_id = stream_id + self._closed = False + + def __iter__(self) -> typing.Iterator[bytes]: + kwargs = {"request": self._request, "stream_id": self._stream_id} + try: + with Trace("receive_response_body", logger, self._request, kwargs): + for chunk in self._connection._receive_response_body( + request=self._request, stream_id=self._stream_id + ): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + with ShieldCancellation(): + self.close() + raise exc + + def close(self) -> None: + if not self._closed: + self._closed = True + kwargs = {"stream_id": self._stream_id} + with Trace("response_closed", logger, self._request, kwargs): + self._connection._response_closed(stream_id=self._stream_id) diff --git a/.venv/Lib/site-packages/httpcore/_sync/http_proxy.py b/.venv/Lib/site-packages/httpcore/_sync/http_proxy.py new file mode 100644 index 0000000..ecca88f --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_sync/http_proxy.py @@ -0,0 +1,367 @@ +from __future__ import annotations + +import base64 +import logging +import ssl +import typing + +from .._backends.base import SOCKET_OPTION, NetworkBackend +from .._exceptions import ProxyError +from .._models import ( + URL, + Origin, + Request, + Response, + enforce_bytes, + enforce_headers, + enforce_url, +) +from .._ssl import default_ssl_context +from .._synchronization import Lock +from .._trace import Trace +from .connection import HTTPConnection +from .connection_pool import ConnectionPool +from .http11 import HTTP11Connection +from .interfaces import ConnectionInterface + +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] + + +logger = logging.getLogger("httpcore.proxy") + + +def merge_headers( + default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, +) -> list[tuple[bytes, bytes]]: + """ + Append default_headers and override_headers, de-duplicating if a key exists + in both cases. + """ + default_headers = [] if default_headers is None else list(default_headers) + override_headers = [] if override_headers is None else list(override_headers) + has_override = set(key.lower() for key, value in override_headers) + default_headers = [ + (key, value) + for key, value in default_headers + if key.lower() not in has_override + ] + return default_headers + override_headers + + +class HTTPProxy(ConnectionPool): # pragma: nocover + """ + A connection pool that sends requests via an HTTP proxy. + """ + + def __init__( + self, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + proxy_url: The URL to use when connecting to the proxy server. + For example `"http://127.0.0.1:8080/"`. + proxy_auth: Any proxy authentication as a two-tuple of + (username, password). May be either bytes or ascii-only str. + proxy_headers: Any HTTP headers to use for the proxy requests. + For example `{"Proxy-Authorization": "Basic :"}`. + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish + a connection. + local_address: Local address to connect from. Can also be used to + connect using a particular address family. Using + `local_address="0.0.0.0"` will connect using an `AF_INET` address + (IPv4), while using `local_address="::"` will connect using an + `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + """ + super().__init__( + ssl_context=ssl_context, + max_connections=max_connections, + max_keepalive_connections=max_keepalive_connections, + keepalive_expiry=keepalive_expiry, + http1=http1, + http2=http2, + network_backend=network_backend, + retries=retries, + local_address=local_address, + uds=uds, + socket_options=socket_options, + ) + + self._proxy_url = enforce_url(proxy_url, name="proxy_url") + if ( + self._proxy_url.scheme == b"http" and proxy_ssl_context is not None + ): # pragma: no cover + raise RuntimeError( + "The `proxy_ssl_context` argument is not allowed for the http scheme" + ) + + self._ssl_context = ssl_context + self._proxy_ssl_context = proxy_ssl_context + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + if proxy_auth is not None: + username = enforce_bytes(proxy_auth[0], name="proxy_auth") + password = enforce_bytes(proxy_auth[1], name="proxy_auth") + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) + self._proxy_headers = [ + (b"Proxy-Authorization", authorization) + ] + self._proxy_headers + + def create_connection(self, origin: Origin) -> ConnectionInterface: + if origin.scheme == b"http": + return ForwardHTTPConnection( + proxy_origin=self._proxy_url.origin, + proxy_headers=self._proxy_headers, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + proxy_ssl_context=self._proxy_ssl_context, + ) + return TunnelHTTPConnection( + proxy_origin=self._proxy_url.origin, + proxy_headers=self._proxy_headers, + remote_origin=origin, + ssl_context=self._ssl_context, + proxy_ssl_context=self._proxy_ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + +class ForwardHTTPConnection(ConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + keepalive_expiry: float | None = None, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + ) -> None: + self._connection = HTTPConnection( + origin=proxy_origin, + keepalive_expiry=keepalive_expiry, + network_backend=network_backend, + socket_options=socket_options, + ssl_context=proxy_ssl_context, + ) + self._proxy_origin = proxy_origin + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + self._remote_origin = remote_origin + + def handle_request(self, request: Request) -> Response: + headers = merge_headers(self._proxy_headers, request.headers) + url = URL( + scheme=self._proxy_origin.scheme, + host=self._proxy_origin.host, + port=self._proxy_origin.port, + target=bytes(request.url), + ) + proxy_request = Request( + method=request.method, + url=url, + headers=headers, + content=request.stream, + extensions=request.extensions, + ) + return self._connection.handle_request(proxy_request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + def close(self) -> None: + self._connection.close() + + def info(self) -> str: + return self._connection.info() + + def is_available(self) -> bool: + return self._connection.is_available() + + def has_expired(self) -> bool: + return self._connection.has_expired() + + def is_idle(self) -> bool: + return self._connection.is_idle() + + def is_closed(self) -> bool: + return self._connection.is_closed() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" + + +class TunnelHTTPConnection(ConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + network_backend: NetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + self._connection: ConnectionInterface = HTTPConnection( + origin=proxy_origin, + keepalive_expiry=keepalive_expiry, + network_backend=network_backend, + socket_options=socket_options, + ssl_context=proxy_ssl_context, + ) + self._proxy_origin = proxy_origin + self._remote_origin = remote_origin + self._ssl_context = ssl_context + self._proxy_ssl_context = proxy_ssl_context + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._connect_lock = Lock() + self._connected = False + + def handle_request(self, request: Request) -> Response: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("connect", None) + + with self._connect_lock: + if not self._connected: + target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port) + + connect_url = URL( + scheme=self._proxy_origin.scheme, + host=self._proxy_origin.host, + port=self._proxy_origin.port, + target=target, + ) + connect_headers = merge_headers( + [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers + ) + connect_request = Request( + method=b"CONNECT", + url=connect_url, + headers=connect_headers, + extensions=request.extensions, + ) + connect_response = self._connection.handle_request( + connect_request + ) + + if connect_response.status < 200 or connect_response.status > 299: + reason_bytes = connect_response.extensions.get("reason_phrase", b"") + reason_str = reason_bytes.decode("ascii", errors="ignore") + msg = "%d %s" % (connect_response.status, reason_str) + self._connection.close() + raise ProxyError(msg) + + stream = connect_response.extensions["network_stream"] + + # Upgrade the stream to SSL + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + with Trace("start_tls", logger, request, kwargs) as trace: + stream = stream.start_tls(**kwargs) + trace.return_value = stream + + # Determine if we should be using HTTP/1.1 or HTTP/2 + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + + # Create the HTTP/1.1 or HTTP/2 connection + if http2_negotiated or (self._http2 and not self._http1): + from .http2 import HTTP2Connection + + self._connection = HTTP2Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = HTTP11Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + + self._connected = True + return self._connection.handle_request(request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + def close(self) -> None: + self._connection.close() + + def info(self) -> str: + return self._connection.info() + + def is_available(self) -> bool: + return self._connection.is_available() + + def has_expired(self) -> bool: + return self._connection.has_expired() + + def is_idle(self) -> bool: + return self._connection.is_idle() + + def is_closed(self) -> bool: + return self._connection.is_closed() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" diff --git a/.venv/Lib/site-packages/httpcore/_sync/interfaces.py b/.venv/Lib/site-packages/httpcore/_sync/interfaces.py new file mode 100644 index 0000000..e673d4c --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_sync/interfaces.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import contextlib +import typing + +from .._models import ( + URL, + Extensions, + HeaderTypes, + Origin, + Request, + Response, + enforce_bytes, + enforce_headers, + enforce_url, + include_request_headers, +) + + +class RequestInterface: + def request( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> Response: + # Strict type checking on our parameters. + method = enforce_bytes(method, name="method") + url = enforce_url(url, name="url") + headers = enforce_headers(headers, name="headers") + + # Include Host header, and optionally Content-Length or Transfer-Encoding. + headers = include_request_headers(headers, url=url, content=content) + + request = Request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + response = self.handle_request(request) + try: + response.read() + finally: + response.close() + return response + + @contextlib.contextmanager + def stream( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> typing.Iterator[Response]: + # Strict type checking on our parameters. + method = enforce_bytes(method, name="method") + url = enforce_url(url, name="url") + headers = enforce_headers(headers, name="headers") + + # Include Host header, and optionally Content-Length or Transfer-Encoding. + headers = include_request_headers(headers, url=url, content=content) + + request = Request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + response = self.handle_request(request) + try: + yield response + finally: + response.close() + + def handle_request(self, request: Request) -> Response: + raise NotImplementedError() # pragma: nocover + + +class ConnectionInterface(RequestInterface): + def close(self) -> None: + raise NotImplementedError() # pragma: nocover + + def info(self) -> str: + raise NotImplementedError() # pragma: nocover + + def can_handle_request(self, origin: Origin) -> bool: + raise NotImplementedError() # pragma: nocover + + def is_available(self) -> bool: + """ + Return `True` if the connection is currently able to accept an + outgoing request. + + An HTTP/1.1 connection will only be available if it is currently idle. + + An HTTP/2 connection will be available so long as the stream ID space is + not yet exhausted, and the connection is not in an error state. + + While the connection is being established we may not yet know if it is going + to result in an HTTP/1.1 or HTTP/2 connection. The connection should be + treated as being available, but might ultimately raise `NewConnectionRequired` + required exceptions if multiple requests are attempted over a connection + that ends up being established as HTTP/1.1. + """ + raise NotImplementedError() # pragma: nocover + + def has_expired(self) -> bool: + """ + Return `True` if the connection is in a state where it should be closed. + + This either means that the connection is idle and it has passed the + expiry time on its keep-alive, or that server has sent an EOF. + """ + raise NotImplementedError() # pragma: nocover + + def is_idle(self) -> bool: + """ + Return `True` if the connection is currently idle. + """ + raise NotImplementedError() # pragma: nocover + + def is_closed(self) -> bool: + """ + Return `True` if the connection has been closed. + + Used when a response is closed to determine if the connection may be + returned to the connection pool or not. + """ + raise NotImplementedError() # pragma: nocover diff --git a/.venv/Lib/site-packages/httpcore/_sync/socks_proxy.py b/.venv/Lib/site-packages/httpcore/_sync/socks_proxy.py new file mode 100644 index 0000000..0ca96dd --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_sync/socks_proxy.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import logging +import ssl + +import socksio + +from .._backends.sync import SyncBackend +from .._backends.base import NetworkBackend, NetworkStream +from .._exceptions import ConnectionNotAvailable, ProxyError +from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url +from .._ssl import default_ssl_context +from .._synchronization import Lock +from .._trace import Trace +from .connection_pool import ConnectionPool +from .http11 import HTTP11Connection +from .interfaces import ConnectionInterface + +logger = logging.getLogger("httpcore.socks") + + +AUTH_METHODS = { + b"\x00": "NO AUTHENTICATION REQUIRED", + b"\x01": "GSSAPI", + b"\x02": "USERNAME/PASSWORD", + b"\xff": "NO ACCEPTABLE METHODS", +} + +REPLY_CODES = { + b"\x00": "Succeeded", + b"\x01": "General SOCKS server failure", + b"\x02": "Connection not allowed by ruleset", + b"\x03": "Network unreachable", + b"\x04": "Host unreachable", + b"\x05": "Connection refused", + b"\x06": "TTL expired", + b"\x07": "Command not supported", + b"\x08": "Address type not supported", +} + + +def _init_socks5_connection( + stream: NetworkStream, + *, + host: bytes, + port: int, + auth: tuple[bytes, bytes] | None = None, +) -> None: + conn = socksio.socks5.SOCKS5Connection() + + # Auth method request + auth_method = ( + socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED + if auth is None + else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD + ) + conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method])) + outgoing_bytes = conn.data_to_send() + stream.write(outgoing_bytes) + + # Auth method response + incoming_bytes = stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5AuthReply) + if response.method != auth_method: + requested = AUTH_METHODS.get(auth_method, "UNKNOWN") + responded = AUTH_METHODS.get(response.method, "UNKNOWN") + raise ProxyError( + f"Requested {requested} from proxy server, but got {responded}." + ) + + if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: + # Username/password request + assert auth is not None + username, password = auth + conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password)) + outgoing_bytes = conn.data_to_send() + stream.write(outgoing_bytes) + + # Username/password response + incoming_bytes = stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply) + if not response.success: + raise ProxyError("Invalid username/password") + + # Connect request + conn.send( + socksio.socks5.SOCKS5CommandRequest.from_address( + socksio.socks5.SOCKS5Command.CONNECT, (host, port) + ) + ) + outgoing_bytes = conn.data_to_send() + stream.write(outgoing_bytes) + + # Connect response + incoming_bytes = stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5Reply) + if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED: + reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN") + raise ProxyError(f"Proxy Server could not connect: {reply_code}.") + + +class SOCKSProxy(ConnectionPool): # pragma: nocover + """ + A connection pool that sends requests via an HTTP proxy. + """ + + def __init__( + self, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + network_backend: NetworkBackend | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + proxy_url: The URL to use when connecting to the proxy server. + For example `"http://127.0.0.1:8080/"`. + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish + a connection. + local_address: Local address to connect from. Can also be used to + connect using a particular address family. Using + `local_address="0.0.0.0"` will connect using an `AF_INET` address + (IPv4), while using `local_address="::"` will connect using an + `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + """ + super().__init__( + ssl_context=ssl_context, + max_connections=max_connections, + max_keepalive_connections=max_keepalive_connections, + keepalive_expiry=keepalive_expiry, + http1=http1, + http2=http2, + network_backend=network_backend, + retries=retries, + ) + self._ssl_context = ssl_context + self._proxy_url = enforce_url(proxy_url, name="proxy_url") + if proxy_auth is not None: + username, password = proxy_auth + username_bytes = enforce_bytes(username, name="proxy_auth") + password_bytes = enforce_bytes(password, name="proxy_auth") + self._proxy_auth: tuple[bytes, bytes] | None = ( + username_bytes, + password_bytes, + ) + else: + self._proxy_auth = None + + def create_connection(self, origin: Origin) -> ConnectionInterface: + return Socks5Connection( + proxy_origin=self._proxy_url.origin, + remote_origin=origin, + proxy_auth=self._proxy_auth, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + +class Socks5Connection(ConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + proxy_auth: tuple[bytes, bytes] | None = None, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + network_backend: NetworkBackend | None = None, + ) -> None: + self._proxy_origin = proxy_origin + self._remote_origin = remote_origin + self._proxy_auth = proxy_auth + self._ssl_context = ssl_context + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + + self._network_backend: NetworkBackend = ( + SyncBackend() if network_backend is None else network_backend + ) + self._connect_lock = Lock() + self._connection: ConnectionInterface | None = None + self._connect_failed = False + + def handle_request(self, request: Request) -> Response: + timeouts = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname", None) + timeout = timeouts.get("connect", None) + + with self._connect_lock: + if self._connection is None: + try: + # Connect to the proxy + kwargs = { + "host": self._proxy_origin.host.decode("ascii"), + "port": self._proxy_origin.port, + "timeout": timeout, + } + with Trace("connect_tcp", logger, request, kwargs) as trace: + stream = self._network_backend.connect_tcp(**kwargs) + trace.return_value = stream + + # Connect to the remote host using socks5 + kwargs = { + "stream": stream, + "host": self._remote_origin.host.decode("ascii"), + "port": self._remote_origin.port, + "auth": self._proxy_auth, + } + with Trace( + "setup_socks5_connection", logger, request, kwargs + ) as trace: + _init_socks5_connection(**kwargs) + trace.return_value = stream + + # Upgrade the stream to SSL + if self._remote_origin.scheme == b"https": + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ( + ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ) + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": sni_hostname + or self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + with Trace("start_tls", logger, request, kwargs) as trace: + stream = stream.start_tls(**kwargs) + trace.return_value = stream + + # Determine if we should be using HTTP/1.1 or HTTP/2 + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + + # Create the HTTP/1.1 or HTTP/2 connection + if http2_negotiated or ( + self._http2 and not self._http1 + ): # pragma: nocover + from .http2 import HTTP2Connection + + self._connection = HTTP2Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = HTTP11Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + except Exception as exc: + self._connect_failed = True + raise exc + elif not self._connection.is_available(): # pragma: nocover + raise ConnectionNotAvailable() + + return self._connection.handle_request(request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + def close(self) -> None: + if self._connection is not None: + self._connection.close() + + def is_available(self) -> bool: + if self._connection is None: # pragma: nocover + # If HTTP/2 support is enabled, and the resulting connection could + # end up as HTTP/2 then we should indicate the connection as being + # available to service multiple requests. + return ( + self._http2 + and (self._remote_origin.scheme == b"https" or not self._http1) + and not self._connect_failed + ) + return self._connection.is_available() + + def has_expired(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.has_expired() + + def is_idle(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.is_idle() + + def is_closed(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.is_closed() + + def info(self) -> str: + if self._connection is None: # pragma: nocover + return "CONNECTION FAILED" if self._connect_failed else "CONNECTING" + return self._connection.info() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" diff --git a/.venv/Lib/site-packages/httpcore/_synchronization.py b/.venv/Lib/site-packages/httpcore/_synchronization.py new file mode 100644 index 0000000..2ecc9e9 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_synchronization.py @@ -0,0 +1,318 @@ +from __future__ import annotations + +import threading +import types + +from ._exceptions import ExceptionMapping, PoolTimeout, map_exceptions + +# Our async synchronization primatives use either 'anyio' or 'trio' depending +# on if they're running under asyncio or trio. + +try: + import trio +except (ImportError, NotImplementedError): # pragma: nocover + trio = None # type: ignore + +try: + import anyio +except ImportError: # pragma: nocover + anyio = None # type: ignore + + +def current_async_library() -> str: + # Determine if we're running under trio or asyncio. + # See https://sniffio.readthedocs.io/en/latest/ + try: + import sniffio + except ImportError: # pragma: nocover + environment = "asyncio" + else: + environment = sniffio.current_async_library() + + if environment not in ("asyncio", "trio"): # pragma: nocover + raise RuntimeError("Running under an unsupported async environment.") + + if environment == "asyncio" and anyio is None: # pragma: nocover + raise RuntimeError( + "Running with asyncio requires installation of 'httpcore[asyncio]'." + ) + + if environment == "trio" and trio is None: # pragma: nocover + raise RuntimeError( + "Running with trio requires installation of 'httpcore[trio]'." + ) + + return environment + + +class AsyncLock: + """ + This is a standard lock. + + In the sync case `Lock` provides thread locking. + In the async case `AsyncLock` provides async locking. + """ + + def __init__(self) -> None: + self._backend = "" + + def setup(self) -> None: + """ + Detect if we're running under 'asyncio' or 'trio' and create + a lock with the correct implementation. + """ + self._backend = current_async_library() + if self._backend == "trio": + self._trio_lock = trio.Lock() + elif self._backend == "asyncio": + self._anyio_lock = anyio.Lock() + + async def __aenter__(self) -> AsyncLock: + if not self._backend: + self.setup() + + if self._backend == "trio": + await self._trio_lock.acquire() + elif self._backend == "asyncio": + await self._anyio_lock.acquire() + + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + if self._backend == "trio": + self._trio_lock.release() + elif self._backend == "asyncio": + self._anyio_lock.release() + + +class AsyncThreadLock: + """ + This is a threading-only lock for no-I/O contexts. + + In the sync case `ThreadLock` provides thread locking. + In the async case `AsyncThreadLock` is a no-op. + """ + + def __enter__(self) -> AsyncThreadLock: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + pass + + +class AsyncEvent: + def __init__(self) -> None: + self._backend = "" + + def setup(self) -> None: + """ + Detect if we're running under 'asyncio' or 'trio' and create + a lock with the correct implementation. + """ + self._backend = current_async_library() + if self._backend == "trio": + self._trio_event = trio.Event() + elif self._backend == "asyncio": + self._anyio_event = anyio.Event() + + def set(self) -> None: + if not self._backend: + self.setup() + + if self._backend == "trio": + self._trio_event.set() + elif self._backend == "asyncio": + self._anyio_event.set() + + async def wait(self, timeout: float | None = None) -> None: + if not self._backend: + self.setup() + + if self._backend == "trio": + trio_exc_map: ExceptionMapping = {trio.TooSlowError: PoolTimeout} + timeout_or_inf = float("inf") if timeout is None else timeout + with map_exceptions(trio_exc_map): + with trio.fail_after(timeout_or_inf): + await self._trio_event.wait() + elif self._backend == "asyncio": + anyio_exc_map: ExceptionMapping = {TimeoutError: PoolTimeout} + with map_exceptions(anyio_exc_map): + with anyio.fail_after(timeout): + await self._anyio_event.wait() + + +class AsyncSemaphore: + def __init__(self, bound: int) -> None: + self._bound = bound + self._backend = "" + + def setup(self) -> None: + """ + Detect if we're running under 'asyncio' or 'trio' and create + a semaphore with the correct implementation. + """ + self._backend = current_async_library() + if self._backend == "trio": + self._trio_semaphore = trio.Semaphore( + initial_value=self._bound, max_value=self._bound + ) + elif self._backend == "asyncio": + self._anyio_semaphore = anyio.Semaphore( + initial_value=self._bound, max_value=self._bound + ) + + async def acquire(self) -> None: + if not self._backend: + self.setup() + + if self._backend == "trio": + await self._trio_semaphore.acquire() + elif self._backend == "asyncio": + await self._anyio_semaphore.acquire() + + async def release(self) -> None: + if self._backend == "trio": + self._trio_semaphore.release() + elif self._backend == "asyncio": + self._anyio_semaphore.release() + + +class AsyncShieldCancellation: + # For certain portions of our codebase where we're dealing with + # closing connections during exception handling we want to shield + # the operation from being cancelled. + # + # with AsyncShieldCancellation(): + # ... # clean-up operations, shielded from cancellation. + + def __init__(self) -> None: + """ + Detect if we're running under 'asyncio' or 'trio' and create + a shielded scope with the correct implementation. + """ + self._backend = current_async_library() + + if self._backend == "trio": + self._trio_shield = trio.CancelScope(shield=True) + elif self._backend == "asyncio": + self._anyio_shield = anyio.CancelScope(shield=True) + + def __enter__(self) -> AsyncShieldCancellation: + if self._backend == "trio": + self._trio_shield.__enter__() + elif self._backend == "asyncio": + self._anyio_shield.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + if self._backend == "trio": + self._trio_shield.__exit__(exc_type, exc_value, traceback) + elif self._backend == "asyncio": + self._anyio_shield.__exit__(exc_type, exc_value, traceback) + + +# Our thread-based synchronization primitives... + + +class Lock: + """ + This is a standard lock. + + In the sync case `Lock` provides thread locking. + In the async case `AsyncLock` provides async locking. + """ + + def __init__(self) -> None: + self._lock = threading.Lock() + + def __enter__(self) -> Lock: + self._lock.acquire() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self._lock.release() + + +class ThreadLock: + """ + This is a threading-only lock for no-I/O contexts. + + In the sync case `ThreadLock` provides thread locking. + In the async case `AsyncThreadLock` is a no-op. + """ + + def __init__(self) -> None: + self._lock = threading.Lock() + + def __enter__(self) -> ThreadLock: + self._lock.acquire() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + self._lock.release() + + +class Event: + def __init__(self) -> None: + self._event = threading.Event() + + def set(self) -> None: + self._event.set() + + def wait(self, timeout: float | None = None) -> None: + if timeout == float("inf"): # pragma: no cover + timeout = None + if not self._event.wait(timeout=timeout): + raise PoolTimeout() # pragma: nocover + + +class Semaphore: + def __init__(self, bound: int) -> None: + self._semaphore = threading.Semaphore(value=bound) + + def acquire(self) -> None: + self._semaphore.acquire() + + def release(self) -> None: + self._semaphore.release() + + +class ShieldCancellation: + # Thread-synchronous codebases don't support cancellation semantics. + # We have this class because we need to mirror the async and sync + # cases within our package, but it's just a no-op. + def __enter__(self) -> ShieldCancellation: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + pass diff --git a/.venv/Lib/site-packages/httpcore/_trace.py b/.venv/Lib/site-packages/httpcore/_trace.py new file mode 100644 index 0000000..5f1cd7c --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_trace.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import inspect +import logging +import types +import typing + +from ._models import Request + + +class Trace: + def __init__( + self, + name: str, + logger: logging.Logger, + request: Request | None = None, + kwargs: dict[str, typing.Any] | None = None, + ) -> None: + self.name = name + self.logger = logger + self.trace_extension = ( + None if request is None else request.extensions.get("trace") + ) + self.debug = self.logger.isEnabledFor(logging.DEBUG) + self.kwargs = kwargs or {} + self.return_value: typing.Any = None + self.should_trace = self.debug or self.trace_extension is not None + self.prefix = self.logger.name.split(".")[-1] + + def trace(self, name: str, info: dict[str, typing.Any]) -> None: + if self.trace_extension is not None: + prefix_and_name = f"{self.prefix}.{name}" + ret = self.trace_extension(prefix_and_name, info) + if inspect.iscoroutine(ret): # pragma: no cover + raise TypeError( + "If you are using a synchronous interface, " + "the callback of the `trace` extension should " + "be a normal function instead of an asynchronous function." + ) + + if self.debug: + if not info or "return_value" in info and info["return_value"] is None: + message = name + else: + args = " ".join([f"{key}={value!r}" for key, value in info.items()]) + message = f"{name} {args}" + self.logger.debug(message) + + def __enter__(self) -> Trace: + if self.should_trace: + info = self.kwargs + self.trace(f"{self.name}.started", info) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + if self.should_trace: + if exc_value is None: + info = {"return_value": self.return_value} + self.trace(f"{self.name}.complete", info) + else: + info = {"exception": exc_value} + self.trace(f"{self.name}.failed", info) + + async def atrace(self, name: str, info: dict[str, typing.Any]) -> None: + if self.trace_extension is not None: + prefix_and_name = f"{self.prefix}.{name}" + coro = self.trace_extension(prefix_and_name, info) + if not inspect.iscoroutine(coro): # pragma: no cover + raise TypeError( + "If you're using an asynchronous interface, " + "the callback of the `trace` extension should " + "be an asynchronous function rather than a normal function." + ) + await coro + + if self.debug: + if not info or "return_value" in info and info["return_value"] is None: + message = name + else: + args = " ".join([f"{key}={value!r}" for key, value in info.items()]) + message = f"{name} {args}" + self.logger.debug(message) + + async def __aenter__(self) -> Trace: + if self.should_trace: + info = self.kwargs + await self.atrace(f"{self.name}.started", info) + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + if self.should_trace: + if exc_value is None: + info = {"return_value": self.return_value} + await self.atrace(f"{self.name}.complete", info) + else: + info = {"exception": exc_value} + await self.atrace(f"{self.name}.failed", info) diff --git a/.venv/Lib/site-packages/httpcore/_utils.py b/.venv/Lib/site-packages/httpcore/_utils.py new file mode 100644 index 0000000..c44ff93 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_utils.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +import select +import socket +import sys + + +def is_socket_readable(sock: socket.socket | None) -> bool: + """ + Return whether a socket, as identifed by its file descriptor, is readable. + "A socket is readable" means that the read buffer isn't empty, i.e. that calling + .recv() on it would immediately return some data. + """ + # NOTE: we want check for readability without actually attempting to read, because + # we don't want to block forever if it's not readable. + + # In the case that the socket no longer exists, or cannot return a file + # descriptor, we treat it as being readable, as if it the next read operation + # on it is ready to return the terminating `b""`. + sock_fd = None if sock is None else sock.fileno() + if sock_fd is None or sock_fd < 0: # pragma: nocover + return True + + # The implementation below was stolen from: + # https://github.com/python-trio/trio/blob/20ee2b1b7376db637435d80e266212a35837ddcc/trio/_socket.py#L471-L478 + # See also: https://github.com/encode/httpcore/pull/193#issuecomment-703129316 + + # Use select.select on Windows, and when poll is unavailable and select.poll + # everywhere else. (E.g. When eventlet is in use. See #327) + if ( + sys.platform == "win32" or getattr(select, "poll", None) is None + ): # pragma: nocover + rready, _, _ = select.select([sock_fd], [], [], 0) + return bool(rready) + p = select.poll() + p.register(sock_fd, select.POLLIN) + return bool(p.poll(0)) diff --git a/.venv/Lib/site-packages/httpcore/py.typed b/.venv/Lib/site-packages/httpcore/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/httpx-0.28.1.dist-info/INSTALLER b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/httpx-0.28.1.dist-info/METADATA b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/METADATA new file mode 100644 index 0000000..b0d2b19 --- /dev/null +++ b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/METADATA @@ -0,0 +1,203 @@ +Metadata-Version: 2.3 +Name: httpx +Version: 0.28.1 +Summary: The next generation HTTP client. +Project-URL: Changelog, https://github.com/encode/httpx/blob/master/CHANGELOG.md +Project-URL: Documentation, https://www.python-httpx.org +Project-URL: Homepage, https://github.com/encode/httpx +Project-URL: Source, https://github.com/encode/httpx +Author-email: Tom Christie +License: BSD-3-Clause +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Web Environment +Classifier: Framework :: AsyncIO +Classifier: Framework :: Trio +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.8 +Requires-Dist: anyio +Requires-Dist: certifi +Requires-Dist: httpcore==1.* +Requires-Dist: idna +Provides-Extra: brotli +Requires-Dist: brotli; (platform_python_implementation == 'CPython') and extra == 'brotli' +Requires-Dist: brotlicffi; (platform_python_implementation != 'CPython') and extra == 'brotli' +Provides-Extra: cli +Requires-Dist: click==8.*; extra == 'cli' +Requires-Dist: pygments==2.*; extra == 'cli' +Requires-Dist: rich<14,>=10; extra == 'cli' +Provides-Extra: http2 +Requires-Dist: h2<5,>=3; extra == 'http2' +Provides-Extra: socks +Requires-Dist: socksio==1.*; extra == 'socks' +Provides-Extra: zstd +Requires-Dist: zstandard>=0.18.0; extra == 'zstd' +Description-Content-Type: text/markdown + +

+ HTTPX +

+ +

HTTPX - A next-generation HTTP client for Python.

+ +

+ + Test Suite + + + Package version + +

+ +HTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync and async APIs**. + +--- + +Install HTTPX using pip: + +```shell +$ pip install httpx +``` + +Now, let's get started: + +```pycon +>>> import httpx +>>> r = httpx.get('https://www.example.org/') +>>> r + +>>> r.status_code +200 +>>> r.headers['content-type'] +'text/html; charset=UTF-8' +>>> r.text +'\n\n\nExample Domain...' +``` + +Or, using the command-line client. + +```shell +$ pip install 'httpx[cli]' # The command line client is an optional dependency. +``` + +Which now allows us to use HTTPX directly from the command-line... + +

+ httpx --help +

+ +Sending a request... + +

+ httpx http://httpbin.org/json +

+ +## Features + +HTTPX builds on the well-established usability of `requests`, and gives you: + +* A broadly [requests-compatible API](https://www.python-httpx.org/compatibility/). +* An integrated command-line client. +* HTTP/1.1 [and HTTP/2 support](https://www.python-httpx.org/http2/). +* Standard synchronous interface, but with [async support if you need it](https://www.python-httpx.org/async/). +* Ability to make requests directly to [WSGI applications](https://www.python-httpx.org/advanced/transports/#wsgi-transport) or [ASGI applications](https://www.python-httpx.org/advanced/transports/#asgi-transport). +* Strict timeouts everywhere. +* Fully type annotated. +* 100% test coverage. + +Plus all the standard features of `requests`... + +* International Domains and URLs +* Keep-Alive & Connection Pooling +* Sessions with Cookie Persistence +* Browser-style SSL Verification +* Basic/Digest Authentication +* Elegant Key/Value Cookies +* Automatic Decompression +* Automatic Content Decoding +* Unicode Response Bodies +* Multipart File Uploads +* HTTP(S) Proxy Support +* Connection Timeouts +* Streaming Downloads +* .netrc Support +* Chunked Requests + +## Installation + +Install with pip: + +```shell +$ pip install httpx +``` + +Or, to include the optional HTTP/2 support, use: + +```shell +$ pip install httpx[http2] +``` + +HTTPX requires Python 3.8+. + +## Documentation + +Project documentation is available at [https://www.python-httpx.org/](https://www.python-httpx.org/). + +For a run-through of all the basics, head over to the [QuickStart](https://www.python-httpx.org/quickstart/). + +For more advanced topics, see the [Advanced Usage](https://www.python-httpx.org/advanced/) section, the [async support](https://www.python-httpx.org/async/) section, or the [HTTP/2](https://www.python-httpx.org/http2/) section. + +The [Developer Interface](https://www.python-httpx.org/api/) provides a comprehensive API reference. + +To find out about tools that integrate with HTTPX, see [Third Party Packages](https://www.python-httpx.org/third_party_packages/). + +## Contribute + +If you want to contribute with HTTPX check out the [Contributing Guide](https://www.python-httpx.org/contributing/) to learn how to start. + +## Dependencies + +The HTTPX project relies on these excellent libraries: + +* `httpcore` - The underlying transport implementation for `httpx`. + * `h11` - HTTP/1.1 support. +* `certifi` - SSL certificates. +* `idna` - Internationalized domain name support. +* `sniffio` - Async library autodetection. + +As well as these optional installs: + +* `h2` - HTTP/2 support. *(Optional, with `httpx[http2]`)* +* `socksio` - SOCKS proxy support. *(Optional, with `httpx[socks]`)* +* `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)* +* `click` - Command line client support. *(Optional, with `httpx[cli]`)* +* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx[brotli]`)* +* `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)* + +A huge amount of credit is due to `requests` for the API layout that +much of this work follows, as well as to `urllib3` for plenty of design +inspiration around the lower-level networking details. + +--- + +

HTTPX is BSD licensed code.
Designed & crafted with care.

— 🦋 —

+ +## Release Information + +### Fixed + +* Reintroduced supposedly-private `URLTypes` shortcut. (#2673) + + +--- + +[Full changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md) diff --git a/.venv/Lib/site-packages/httpx-0.28.1.dist-info/RECORD b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/RECORD new file mode 100644 index 0000000..f72347d --- /dev/null +++ b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/RECORD @@ -0,0 +1,55 @@ +../../Scripts/httpx.exe,sha256=6P1zoEd-wmgHMtFyMIapgspwkq711mbtKXsvOyIwuqg,108430 +httpx-0.28.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +httpx-0.28.1.dist-info/METADATA,sha256=_rubD48-gNV8gZnDBPNcQzboWB0dGNeYPJJ2a4J5OyU,7052 +httpx-0.28.1.dist-info/RECORD,, +httpx-0.28.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +httpx-0.28.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87 +httpx-0.28.1.dist-info/entry_points.txt,sha256=2lVkdQmxLA1pNMgSN2eV89o90HCZezhmNwsy6ryKDSA,37 +httpx-0.28.1.dist-info/licenses/LICENSE.md,sha256=TsWdVE8StfU5o6cW_TIaxYzNgDC0ZSIfLIgCAM3yjY0,1508 +httpx/__init__.py,sha256=CsaZe6yZj0rHg6322AWKWHGTMVr9txgEfD5P3_Rrz60,2171 +httpx/__pycache__/__init__.cpython-312.pyc,, +httpx/__pycache__/__version__.cpython-312.pyc,, +httpx/__pycache__/_api.cpython-312.pyc,, +httpx/__pycache__/_auth.cpython-312.pyc,, +httpx/__pycache__/_client.cpython-312.pyc,, +httpx/__pycache__/_config.cpython-312.pyc,, +httpx/__pycache__/_content.cpython-312.pyc,, +httpx/__pycache__/_decoders.cpython-312.pyc,, +httpx/__pycache__/_exceptions.cpython-312.pyc,, +httpx/__pycache__/_main.cpython-312.pyc,, +httpx/__pycache__/_models.cpython-312.pyc,, +httpx/__pycache__/_multipart.cpython-312.pyc,, +httpx/__pycache__/_status_codes.cpython-312.pyc,, +httpx/__pycache__/_types.cpython-312.pyc,, +httpx/__pycache__/_urlparse.cpython-312.pyc,, +httpx/__pycache__/_urls.cpython-312.pyc,, +httpx/__pycache__/_utils.cpython-312.pyc,, +httpx/__version__.py,sha256=LoUyYeOXTieGzuP_64UL0wxdtxjuu_QbOvE7NOg-IqU,108 +httpx/_api.py,sha256=r_Zgs4jIpcPJLqK5dbbSayqo_iVMKFaxZCd-oOHxLEs,11743 +httpx/_auth.py,sha256=Yr3QwaUSK17rGYx-7j-FdicFIzz4Y9FFV-1F4-7RXX4,11891 +httpx/_client.py,sha256=xD-UG67-WMkeltAAOeGGj-cZ2RRTAm19sWRxlFY7_40,65714 +httpx/_config.py,sha256=pPp2U-wicfcKsF-KYRE1LYdt3e6ERGeIoXZ8Gjo3LWc,8547 +httpx/_content.py,sha256=LGGzrJTR3OvN4Mb1GVVNLXkXJH-6oKlwAttO9p5w_yg,8161 +httpx/_decoders.py,sha256=p0dX8I0NEHexs3UGp4SsZutiMhsXrrWl6-GnqVb0iKM,12041 +httpx/_exceptions.py,sha256=bxW7fxzgVMAdNTbwT0Vnq04gJDW1_gI_GFiQPuMyjL0,8527 +httpx/_main.py,sha256=Cg9GMabiTT_swaDfUgIRitSwxLRMSwUDOm7LdSGqlA4,15626 +httpx/_models.py,sha256=4__Guyv1gLxuZChwim8kfQNiIOcJ9acreFOSurvZfms,44700 +httpx/_multipart.py,sha256=KOHEZZl6oohg9mPaKyyu345qq1rJLg35TUG3YAzXB3Y,9843 +httpx/_status_codes.py,sha256=DYn-2ufBgMeXy5s8x3_TB7wjAuAAMewTakPrm5rXEsc,5639 +httpx/_transports/__init__.py,sha256=GbUoBSAOp7z-l-9j5YhMhR3DMIcn6FVLhj072O3Nnno,275 +httpx/_transports/__pycache__/__init__.cpython-312.pyc,, +httpx/_transports/__pycache__/asgi.cpython-312.pyc,, +httpx/_transports/__pycache__/base.cpython-312.pyc,, +httpx/_transports/__pycache__/default.cpython-312.pyc,, +httpx/_transports/__pycache__/mock.cpython-312.pyc,, +httpx/_transports/__pycache__/wsgi.cpython-312.pyc,, +httpx/_transports/asgi.py,sha256=HRfiDYMPt4wQH2gFgHZg4c-i3sblo6bL5GTqcET-xz8,5501 +httpx/_transports/base.py,sha256=kZS_VMbViYfF570pogUCJ1bulz-ybfL51Pqs9yktebU,2523 +httpx/_transports/default.py,sha256=AzeaRUyVwCccTyyNJexDf0n1dFfzzydpdIQgvw7PLnk,13983 +httpx/_transports/mock.py,sha256=PTo0d567RITXxGrki6kN7_67wwAxfwiMDcuXJiZCjEo,1232 +httpx/_transports/wsgi.py,sha256=NcPX3Xap_EwCFZWO_OaSyQNuInCYx1QMNbO8GAei6jY,4825 +httpx/_types.py,sha256=Jyh41GQq7AOev8IOWKDAg7zCbvHAfufmW5g_PiTtErY,2965 +httpx/_urlparse.py,sha256=ZAmH47ONfkxrrj-PPYhGeiHjb6AjKCS-ANWIN4OL_KY,18546 +httpx/_urls.py,sha256=dX99VR1DSOHpgo9Aq7PzYO4FKdxqKjwyNp8grf8dHN0,21550 +httpx/_utils.py,sha256=_TVeqAKvxJkKHdz7dFeb4s0LZqQXgeFkXSgfiHBK_1o,8285 +httpx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/.venv/Lib/site-packages/httpx-0.28.1.dist-info/REQUESTED b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/httpx-0.28.1.dist-info/WHEEL b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/WHEEL new file mode 100644 index 0000000..21aaa72 --- /dev/null +++ b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.26.3 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/Lib/site-packages/httpx-0.28.1.dist-info/entry_points.txt b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/entry_points.txt new file mode 100644 index 0000000..8ae9600 --- /dev/null +++ b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +httpx = httpx:main diff --git a/.venv/Lib/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000..ab79d16 --- /dev/null +++ b/.venv/Lib/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md @@ -0,0 +1,12 @@ +Copyright © 2019, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/Lib/site-packages/httpx/__init__.py b/.venv/Lib/site-packages/httpx/__init__.py new file mode 100644 index 0000000..e9addde --- /dev/null +++ b/.venv/Lib/site-packages/httpx/__init__.py @@ -0,0 +1,105 @@ +from .__version__ import __description__, __title__, __version__ +from ._api import * +from ._auth import * +from ._client import * +from ._config import * +from ._content import * +from ._exceptions import * +from ._models import * +from ._status_codes import * +from ._transports import * +from ._types import * +from ._urls import * + +try: + from ._main import main +except ImportError: # pragma: no cover + + def main() -> None: # type: ignore + import sys + + print( + "The httpx command line client could not run because the required " + "dependencies were not installed.\nMake sure you've installed " + "everything with: pip install 'httpx[cli]'" + ) + sys.exit(1) + + +__all__ = [ + "__description__", + "__title__", + "__version__", + "ASGITransport", + "AsyncBaseTransport", + "AsyncByteStream", + "AsyncClient", + "AsyncHTTPTransport", + "Auth", + "BaseTransport", + "BasicAuth", + "ByteStream", + "Client", + "CloseError", + "codes", + "ConnectError", + "ConnectTimeout", + "CookieConflict", + "Cookies", + "create_ssl_context", + "DecodingError", + "delete", + "DigestAuth", + "get", + "head", + "Headers", + "HTTPError", + "HTTPStatusError", + "HTTPTransport", + "InvalidURL", + "Limits", + "LocalProtocolError", + "main", + "MockTransport", + "NetRCAuth", + "NetworkError", + "options", + "patch", + "PoolTimeout", + "post", + "ProtocolError", + "Proxy", + "ProxyError", + "put", + "QueryParams", + "ReadError", + "ReadTimeout", + "RemoteProtocolError", + "request", + "Request", + "RequestError", + "RequestNotRead", + "Response", + "ResponseNotRead", + "stream", + "StreamClosed", + "StreamConsumed", + "StreamError", + "SyncByteStream", + "Timeout", + "TimeoutException", + "TooManyRedirects", + "TransportError", + "UnsupportedProtocol", + "URL", + "USE_CLIENT_DEFAULT", + "WriteError", + "WriteTimeout", + "WSGITransport", +] + + +__locals = locals() +for __name in __all__: + if not __name.startswith("__"): + setattr(__locals[__name], "__module__", "httpx") # noqa diff --git a/.venv/Lib/site-packages/httpx/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd1049634f10cca00b8aae1849b00a0d610377cb GIT binary patch literal 2136 zcmY*ZOK%)S5boL6%X1!LFWuw+)rtpqEGre^8 z+Fl-#4+senL~i7WI3OVliN6r8oN|Z=9U~5K$<0YXLOD@AvkyG7Yrm@Mu2)ypFO^D( z;CJX<=zMGu@~38oUts{A)(k?vAwKbqkVHnyh%&8AL|VkqvP_u$F*8895z3EHVT8;P zvPP&lLZuP1N2okP6^)A8UpAbGs;#O)wuoQ&&S*_#2uvRiLT$~!)FQd@5+SV`)V5z6 zrZWEZ%|pyaf84lr3h&Os4Ah=O-nabXVKz}=X1C7dNI!Rmz~n20o0wn8&wrL{Rj-p< zZ|{-)>~*qd)a^&wRz0h#l=>h%9&}~EVoFt!QW?k)5ZaXP0vCzAZYbkbB)P~9;x#XI zmQw9m|36(sg4H&B5|U4e97KfiWrmR(;Z@5 zcn}Neh9UUpN>|+-a6}Ikebzg>3(iRA04Cp;ogm(F_5#_t;Bj@3vQin{5$Dc%zFdcMC*23FS>ngE)|s zp6m9xi4GGot=+$fvs2y;9WnMvw z&!~zzuk=b)!wu=mo=63ilR1}G;$1fk{LRLyDy#-kATg3v=D8t0y<{E>lWWPFsmfL6 z?TqVHTm#uyNTMhs++%zv^pN%Qu5-yniz0+rx$^*?35o^-_ZRyZWnVH>*N3|?R$_ENli8xyXwaM z2KcxcaMskssKlwUnN4)j)r}7RFiDlwFE+iou|yYESC-bAba82Uesi^{O4m3JVS2Ni z;GyrzwG%^=!A)ACG-7@)*{bs9@B$jQ#qVqp$b&?yC}@l9>=43s`q)?cJ@l7 zlTTAJDa8$mp%+5kr2?JO0V+@$yAh5Ddf`&&-Kb0f=u+|@2%pf~qoTKm^;VknJG_Ie zUV`FtB*$69F#aHy?vYDJWafy}j>v5CclwAtpMbdpoI=%cu58qP&%N+uZrQLi2j=5O zhLoqjJbz$4oH%u`{J@_2)tw_pCvs@}Hl%-)-HzJ2{C^S*Wdah_CX|2<|I mV$D1uC8PFF&dS%0^N5}ay&CUk3-cGr-HXM`X7=ZTf%HF!53~IM literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/__version__.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/__version__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17ee3ea3593c86080b24dfc1c9e3b6bad8cdb42b GIT binary patch literal 331 zcmYjN%SyvQ6ipgmvDS);>)94+d~He*u~&zG6H^{o&9>LBt?dP4vVCax)cb{lb}VoI2+%=j^^=6*eJ? zsR#%u>V!~`fnZ75uZ1chDG0eL-i&3r$#-Sm~m1*t(o@`W(TP hu=Vir7xGYtFUvH|eCJ?c=lj=-dcJ$~QWcxp_yMkmWh?*y literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_api.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e476e39abf1a23ec117504d7915e4e8d6ff0a440 GIT binary patch literal 10351 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 DlQ`dN literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_auth.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_auth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57b23259deb685ea911403cc6a4334d79973e159 GIT binary patch literal 15603 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_client.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_client.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91348b0f310e7534a08697d824d6d74076ad0845 GIT binary patch literal 64147 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 Zq@ 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∾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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0dc0f70adddaf9a99838aec6cf217b86dc2f4fd4 GIT binary patch literal 16769 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 UoxyS^d3$$7@BPf%_tB957izwZ)Bpeg literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2e17c21076bd0077f9da6b9414d676f92b15da6 GIT binary patch literal 12101 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 Ho3{T49NrR@ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_main.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb357174ff89601db80431cdb95685f947397b43 GIT binary patch literal 20606 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 XOkPO~J*BIA49U(fUSXgUB8vYXr5Zux literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_models.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ad31be6a9304065545be7a2e8c62f981bcaca45 GIT binary patch literal 58266 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 KRs@svWbrS8fH0;2 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba7a16779a7a5f6c519d836b388726af7f1860e3 GIT binary patch literal 13585 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e568cf5ee87e9b23bfa8530d45261ad46bb611d GIT binary patch literal 7206 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 P`#*%Ie;NWBX2<^lI}NYi literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_types.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_types.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..726d42d1d50e25fb72e98ee234eb4642d4f67084 GIT binary patch literal 3843 zcmb7GO>7&-72aL$@@M%^iI!!_*24*zS&*Mk_r^*0DSxY z=FK z@Uq*Ac?MzzteDO!zz>7fS`pSDvmJ{Sk7)Mo*7}?kX9VH|5rg>m{tlW0v+bRUX90g0 z4O;P{`&>tJAI#FgORY9vu{Q6`S61DWA6t?Co4uH@*9gHb;EedpS)vJ=0RL2sljdY>_~e@}_qlJt?6= z7Ma|GQW1i43k9(JMBDJ#TP1xp$(Y<6R-czm&x4$T)RLZbnI0RsW_l)(BqI#WeG*F3 z!QHiR|DNgpbqV3}oP;Gs!zUmf?gNs-#|hg*$T33Vgpgw3ZXjmZ+UqA}9nI?)Ns1Yo zhrPd-v*5Hp;EJ=x-WmA3q7Wt0aN#jBjaBdMe z;X;;dv@=M!1pM{{AfHh*gw*%#GX2)!zoGx&dPXOine}rIR*^80$>g9knaqfwAbnMp z1*N3v!V(g43S?A41s%z$H?-L`qv})dRTCJi7}TtW^2-{DE&O|B{MzDz2H9Mkmz2fZ zNL$m@;^KTxMgmmeG0N)N;vGrTZ_P~!Pc`9AUeeYUC#CEf!oqF!vAURgiV9B`@8y;j zH2`(72qtL-X^X46UVJ{9A?>K8ie=N=0lnUHIKQcm;Sm7RNQM4J{i{O#IW*V`T-|dq zVZKcX|&9*s4}E!{aape#(Zfh3y|Ri2wABhs812bWN-Rv zPU|F3R|SmnNYaoX70N;Z>CaTW20BeG;VjbjgGtMfbwL7))pn!cQG{a$$E}>rL-Jt_ zm1J8-sr@-KZzxPEY^NI!18Z{GJe}B;-6HWPPh0EwTubMh?acs?D%m ztai4Z{(f|i0`?yPmJ3*O2%`Pra=8Dm|NCK zOLh+&JBq^AKY=)!4~TUXTHe6Vn$`UbOXCZmPI~Y~_}~;Fql9<~`GAlyLehjBDZ5n# zLLS3UBbTC6_WH?2$rwyKb*H(YRri_XRnwEn$Z8h4EJTiTDr-F*k0p#r?MycuZn)er zfrdFk+=O@tvG9-`VDirqlRt@8bK)T3LWG0~i4YPcBnHIfG7{YX_QmYUWYxlQZpFT^ zNkIS)^gYUJ_HAz6>*OMbX(bHW{X5@uX*xDtxq@zT%XwAOO?F9D^JZ`k=M;D>pQ786 zE}33b#5#(J=is1`=iKx=lPs+MJGngSc>9Q8M-DhnM5CEeM>-G%Y6#DDCSxCa zs|gM_hUvX--zGQlJS3C63z`=S+;-74y~{8(`%f=L55Hp~H1m!+M0Sa|J4CV3zp#Bj zvLipTmkstZFl{##?XAUYT7A3`-(;HuSB&1Y5&5tZ-0?@M@tUi8!tnQ3ygRX;78`A` z{*Cc!y!vH*weh8K=0>yUh7rBl4BRx>k6WyFBfgtH!*|+X6D`)aFYdxO&|P3I;pa_OV=I*X0f~6@NPd!KzcWa$~5WZH_mdze#NkZI!m? z|Crc*_`_Gmwb^F(tPz^4_;<*n5c%s_g7j=P{c^N+wLbCv$BoI&g*UTX54X_|nI;bi zrU6`=W4Q3Hi;XaqK%1vRUDb3gQG3?xI%5P!Dts#tttP6a+I&4xd-&Z~4X%;h9NTg= z6yxmmX6(8VxKZ)#1R#TUP8O=X!S~e`>SGPAe$6_Awp#jJP?B$bc>AR^~sh(|iCymg#iXZl~ z!-jXP#EiYXSnF%@Lk2s1Kmk3Vy;hq1n89B8FZJftO gcl`a$Q;@0Aiszl%PX~4>An*FUbnx#K5bHMl59SE`)c^nh literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..453412f031554c7564e10bd86efcd2399a3936ec GIT binary patch literal 17531 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__pycache__/_urls.cpython-312.pyc b/.venv/Lib/site-packages/httpx/__pycache__/_urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62490db753fc556c095b85ce1302be558ff79ac2 GIT binary patch literal 27959 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/__version__.py b/.venv/Lib/site-packages/httpx/__version__.py new file mode 100644 index 0000000..801bfac --- /dev/null +++ b/.venv/Lib/site-packages/httpx/__version__.py @@ -0,0 +1,3 @@ +__title__ = "httpx" +__description__ = "A next generation HTTP client, for Python 3." +__version__ = "0.28.1" diff --git a/.venv/Lib/site-packages/httpx/_api.py b/.venv/Lib/site-packages/httpx/_api.py new file mode 100644 index 0000000..c3cda1e --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_api.py @@ -0,0 +1,438 @@ +from __future__ import annotations + +import typing +from contextlib import contextmanager + +from ._client import Client +from ._config import DEFAULT_TIMEOUT_CONFIG +from ._models import Response +from ._types import ( + AuthTypes, + CookieTypes, + HeaderTypes, + ProxyTypes, + QueryParamTypes, + RequestContent, + RequestData, + RequestFiles, + TimeoutTypes, +) +from ._urls import URL + +if typing.TYPE_CHECKING: + import ssl # pragma: no cover + + +__all__ = [ + "delete", + "get", + "head", + "options", + "patch", + "post", + "put", + "request", + "stream", +] + + +def request( + method: str, + url: URL | str, + *, + params: QueryParamTypes | None = None, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + trust_env: bool = True, +) -> Response: + """ + Sends an HTTP request. + + **Parameters:** + + * **method** - HTTP method for the new `Request` object: `GET`, `OPTIONS`, + `HEAD`, `POST`, `PUT`, `PATCH`, or `DELETE`. + * **url** - URL for the new `Request` object. + * **params** - *(optional)* Query parameters to include in the URL, as a + string, dictionary, or sequence of two-tuples. + * **content** - *(optional)* Binary content to include in the body of the + request, as bytes or a byte iterator. + * **data** - *(optional)* Form data to include in the body of the request, + as a dictionary. + * **files** - *(optional)* A dictionary of upload files to include in the + body of the request. + * **json** - *(optional)* A JSON serializable object to include in the body + of the request. + * **headers** - *(optional)* Dictionary of HTTP headers to include in the + request. + * **cookies** - *(optional)* Dictionary of Cookie items to include in the + request. + * **auth** - *(optional)* An authentication class to use when sending the + request. + * **proxy** - *(optional)* A proxy URL where all the traffic should be routed. + * **timeout** - *(optional)* The timeout configuration to use when sending + the request. + * **follow_redirects** - *(optional)* Enables or disables HTTP redirects. + * **verify** - *(optional)* Either `True` to use an SSL context with the + default CA bundle, `False` to disable verification, or an instance of + `ssl.SSLContext` to use a custom context. + * **trust_env** - *(optional)* Enables or disables usage of environment + variables for configuration. + + **Returns:** `Response` + + Usage: + + ``` + >>> import httpx + >>> response = httpx.request('GET', 'https://httpbin.org/get') + >>> response + + ``` + """ + with Client( + cookies=cookies, + proxy=proxy, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) as client: + return client.request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + auth=auth, + follow_redirects=follow_redirects, + ) + + +@contextmanager +def stream( + method: str, + url: URL | str, + *, + params: QueryParamTypes | None = None, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + trust_env: bool = True, +) -> typing.Iterator[Response]: + """ + Alternative to `httpx.request()` that streams the response body + instead of loading it into memory at once. + + **Parameters**: See `httpx.request`. + + See also: [Streaming Responses][0] + + [0]: /quickstart#streaming-responses + """ + with Client( + cookies=cookies, + proxy=proxy, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) as client: + with client.stream( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + auth=auth, + follow_redirects=follow_redirects, + ) as response: + yield response + + +def get( + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `GET` request. + + **Parameters**: See `httpx.request`. + + Note that the `data`, `files`, `json` and `content` parameters are not available + on this function, as `GET` requests should not include a request body. + """ + return request( + "GET", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def options( + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends an `OPTIONS` request. + + **Parameters**: See `httpx.request`. + + Note that the `data`, `files`, `json` and `content` parameters are not available + on this function, as `OPTIONS` requests should not include a request body. + """ + return request( + "OPTIONS", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def head( + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `HEAD` request. + + **Parameters**: See `httpx.request`. + + Note that the `data`, `files`, `json` and `content` parameters are not available + on this function, as `HEAD` requests should not include a request body. + """ + return request( + "HEAD", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def post( + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `POST` request. + + **Parameters**: See `httpx.request`. + """ + return request( + "POST", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def put( + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `PUT` request. + + **Parameters**: See `httpx.request`. + """ + return request( + "PUT", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def patch( + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + verify: ssl.SSLContext | str | bool = True, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + trust_env: bool = True, +) -> Response: + """ + Sends a `PATCH` request. + + **Parameters**: See `httpx.request`. + """ + return request( + "PATCH", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) + + +def delete( + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | None = None, + proxy: ProxyTypes | None = None, + follow_redirects: bool = False, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + verify: ssl.SSLContext | str | bool = True, + trust_env: bool = True, +) -> Response: + """ + Sends a `DELETE` request. + + **Parameters**: See `httpx.request`. + + Note that the `data`, `files`, `json` and `content` parameters are not available + on this function, as `DELETE` requests should not include a request body. + """ + return request( + "DELETE", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + proxy=proxy, + follow_redirects=follow_redirects, + verify=verify, + timeout=timeout, + trust_env=trust_env, + ) diff --git a/.venv/Lib/site-packages/httpx/_auth.py b/.venv/Lib/site-packages/httpx/_auth.py new file mode 100644 index 0000000..b03971a --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_auth.py @@ -0,0 +1,348 @@ +from __future__ import annotations + +import hashlib +import os +import re +import time +import typing +from base64 import b64encode +from urllib.request import parse_http_list + +from ._exceptions import ProtocolError +from ._models import Cookies, Request, Response +from ._utils import to_bytes, to_str, unquote + +if typing.TYPE_CHECKING: # pragma: no cover + from hashlib import _Hash + + +__all__ = ["Auth", "BasicAuth", "DigestAuth", "NetRCAuth"] + + +class Auth: + """ + Base class for all authentication schemes. + + To implement a custom authentication scheme, subclass `Auth` and override + the `.auth_flow()` method. + + If the authentication scheme does I/O such as disk access or network calls, or uses + synchronization primitives such as locks, you should override `.sync_auth_flow()` + and/or `.async_auth_flow()` instead of `.auth_flow()` to provide specialized + implementations that will be used by `Client` and `AsyncClient` respectively. + """ + + requires_request_body = False + requires_response_body = False + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + """ + Execute the authentication flow. + + To dispatch a request, `yield` it: + + ``` + yield request + ``` + + The client will `.send()` the response back into the flow generator. You can + access it like so: + + ``` + response = yield request + ``` + + A `return` (or reaching the end of the generator) will result in the + client returning the last response obtained from the server. + + You can dispatch as many requests as is necessary. + """ + yield request + + def sync_auth_flow( + self, request: Request + ) -> typing.Generator[Request, Response, None]: + """ + Execute the authentication flow synchronously. + + By default, this defers to `.auth_flow()`. You should override this method + when the authentication scheme does I/O and/or uses concurrency primitives. + """ + if self.requires_request_body: + request.read() + + flow = self.auth_flow(request) + request = next(flow) + + while True: + response = yield request + if self.requires_response_body: + response.read() + + try: + request = flow.send(response) + except StopIteration: + break + + async def async_auth_flow( + self, request: Request + ) -> typing.AsyncGenerator[Request, Response]: + """ + Execute the authentication flow asynchronously. + + By default, this defers to `.auth_flow()`. You should override this method + when the authentication scheme does I/O and/or uses concurrency primitives. + """ + if self.requires_request_body: + await request.aread() + + flow = self.auth_flow(request) + request = next(flow) + + while True: + response = yield request + if self.requires_response_body: + await response.aread() + + try: + request = flow.send(response) + except StopIteration: + break + + +class FunctionAuth(Auth): + """ + Allows the 'auth' argument to be passed as a simple callable function, + that takes the request, and returns a new, modified request. + """ + + def __init__(self, func: typing.Callable[[Request], Request]) -> None: + self._func = func + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + yield self._func(request) + + +class BasicAuth(Auth): + """ + Allows the 'auth' argument to be passed as a (username, password) pair, + and uses HTTP Basic authentication. + """ + + def __init__(self, username: str | bytes, password: str | bytes) -> None: + self._auth_header = self._build_auth_header(username, password) + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + request.headers["Authorization"] = self._auth_header + yield request + + def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str: + userpass = b":".join((to_bytes(username), to_bytes(password))) + token = b64encode(userpass).decode() + return f"Basic {token}" + + +class NetRCAuth(Auth): + """ + Use a 'netrc' file to lookup basic auth credentials based on the url host. + """ + + def __init__(self, file: str | None = None) -> None: + # Lazily import 'netrc'. + # There's no need for us to load this module unless 'NetRCAuth' is being used. + import netrc + + self._netrc_info = netrc.netrc(file) + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + auth_info = self._netrc_info.authenticators(request.url.host) + if auth_info is None or not auth_info[2]: + # The netrc file did not have authentication credentials for this host. + yield request + else: + # Build a basic auth header with credentials from the netrc file. + request.headers["Authorization"] = self._build_auth_header( + username=auth_info[0], password=auth_info[2] + ) + yield request + + def _build_auth_header(self, username: str | bytes, password: str | bytes) -> str: + userpass = b":".join((to_bytes(username), to_bytes(password))) + token = b64encode(userpass).decode() + return f"Basic {token}" + + +class DigestAuth(Auth): + _ALGORITHM_TO_HASH_FUNCTION: dict[str, typing.Callable[[bytes], _Hash]] = { + "MD5": hashlib.md5, + "MD5-SESS": hashlib.md5, + "SHA": hashlib.sha1, + "SHA-SESS": hashlib.sha1, + "SHA-256": hashlib.sha256, + "SHA-256-SESS": hashlib.sha256, + "SHA-512": hashlib.sha512, + "SHA-512-SESS": hashlib.sha512, + } + + def __init__(self, username: str | bytes, password: str | bytes) -> None: + self._username = to_bytes(username) + self._password = to_bytes(password) + self._last_challenge: _DigestAuthChallenge | None = None + self._nonce_count = 1 + + def auth_flow(self, request: Request) -> typing.Generator[Request, Response, None]: + if self._last_challenge: + request.headers["Authorization"] = self._build_auth_header( + request, self._last_challenge + ) + + response = yield request + + if response.status_code != 401 or "www-authenticate" not in response.headers: + # If the response is not a 401 then we don't + # need to build an authenticated request. + return + + for auth_header in response.headers.get_list("www-authenticate"): + if auth_header.lower().startswith("digest "): + break + else: + # If the response does not include a 'WWW-Authenticate: Digest ...' + # header, then we don't need to build an authenticated request. + return + + self._last_challenge = self._parse_challenge(request, response, auth_header) + self._nonce_count = 1 + + request.headers["Authorization"] = self._build_auth_header( + request, self._last_challenge + ) + if response.cookies: + Cookies(response.cookies).set_cookie_header(request=request) + yield request + + def _parse_challenge( + self, request: Request, response: Response, auth_header: str + ) -> _DigestAuthChallenge: + """ + Returns a challenge from a Digest WWW-Authenticate header. + These take the form of: + `Digest realm="realm@host.com",qop="auth,auth-int",nonce="abc",opaque="xyz"` + """ + scheme, _, fields = auth_header.partition(" ") + + # This method should only ever have been called with a Digest auth header. + assert scheme.lower() == "digest" + + header_dict: dict[str, str] = {} + for field in parse_http_list(fields): + key, value = field.strip().split("=", 1) + header_dict[key] = unquote(value) + + try: + realm = header_dict["realm"].encode() + nonce = header_dict["nonce"].encode() + algorithm = header_dict.get("algorithm", "MD5") + opaque = header_dict["opaque"].encode() if "opaque" in header_dict else None + qop = header_dict["qop"].encode() if "qop" in header_dict else None + return _DigestAuthChallenge( + realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop + ) + except KeyError as exc: + message = "Malformed Digest WWW-Authenticate header" + raise ProtocolError(message, request=request) from exc + + def _build_auth_header( + self, request: Request, challenge: _DigestAuthChallenge + ) -> str: + hash_func = self._ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm.upper()] + + def digest(data: bytes) -> bytes: + return hash_func(data).hexdigest().encode() + + A1 = b":".join((self._username, challenge.realm, self._password)) + + path = request.url.raw_path + A2 = b":".join((request.method.encode(), path)) + # TODO: implement auth-int + HA2 = digest(A2) + + nc_value = b"%08x" % self._nonce_count + cnonce = self._get_client_nonce(self._nonce_count, challenge.nonce) + self._nonce_count += 1 + + HA1 = digest(A1) + if challenge.algorithm.lower().endswith("-sess"): + HA1 = digest(b":".join((HA1, challenge.nonce, cnonce))) + + qop = self._resolve_qop(challenge.qop, request=request) + if qop is None: + # Following RFC 2069 + digest_data = [HA1, challenge.nonce, HA2] + else: + # Following RFC 2617/7616 + digest_data = [HA1, challenge.nonce, nc_value, cnonce, qop, HA2] + + format_args = { + "username": self._username, + "realm": challenge.realm, + "nonce": challenge.nonce, + "uri": path, + "response": digest(b":".join(digest_data)), + "algorithm": challenge.algorithm.encode(), + } + if challenge.opaque: + format_args["opaque"] = challenge.opaque + if qop: + format_args["qop"] = b"auth" + format_args["nc"] = nc_value + format_args["cnonce"] = cnonce + + return "Digest " + self._get_header_value(format_args) + + def _get_client_nonce(self, nonce_count: int, nonce: bytes) -> bytes: + s = str(nonce_count).encode() + s += nonce + s += time.ctime().encode() + s += os.urandom(8) + + return hashlib.sha1(s).hexdigest()[:16].encode() + + def _get_header_value(self, header_fields: dict[str, bytes]) -> str: + NON_QUOTED_FIELDS = ("algorithm", "qop", "nc") + QUOTED_TEMPLATE = '{}="{}"' + NON_QUOTED_TEMPLATE = "{}={}" + + header_value = "" + for i, (field, value) in enumerate(header_fields.items()): + if i > 0: + header_value += ", " + template = ( + QUOTED_TEMPLATE + if field not in NON_QUOTED_FIELDS + else NON_QUOTED_TEMPLATE + ) + header_value += template.format(field, to_str(value)) + + return header_value + + def _resolve_qop(self, qop: bytes | None, request: Request) -> bytes | None: + if qop is None: + return None + qops = re.split(b", ?", qop) + if b"auth" in qops: + return b"auth" + + if qops == [b"auth-int"]: + raise NotImplementedError("Digest auth-int support is not yet implemented") + + message = f'Unexpected qop value "{qop!r}" in digest auth' + raise ProtocolError(message, request=request) + + +class _DigestAuthChallenge(typing.NamedTuple): + realm: bytes + nonce: bytes + algorithm: str + opaque: bytes | None + qop: bytes | None diff --git a/.venv/Lib/site-packages/httpx/_client.py b/.venv/Lib/site-packages/httpx/_client.py new file mode 100644 index 0000000..2249231 --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_client.py @@ -0,0 +1,2019 @@ +from __future__ import annotations + +import datetime +import enum +import logging +import time +import typing +import warnings +from contextlib import asynccontextmanager, contextmanager +from types import TracebackType + +from .__version__ import __version__ +from ._auth import Auth, BasicAuth, FunctionAuth +from ._config import ( + DEFAULT_LIMITS, + DEFAULT_MAX_REDIRECTS, + DEFAULT_TIMEOUT_CONFIG, + Limits, + Proxy, + Timeout, +) +from ._decoders import SUPPORTED_DECODERS +from ._exceptions import ( + InvalidURL, + RemoteProtocolError, + TooManyRedirects, + request_context, +) +from ._models import Cookies, Headers, Request, Response +from ._status_codes import codes +from ._transports.base import AsyncBaseTransport, BaseTransport +from ._transports.default import AsyncHTTPTransport, HTTPTransport +from ._types import ( + AsyncByteStream, + AuthTypes, + CertTypes, + CookieTypes, + HeaderTypes, + ProxyTypes, + QueryParamTypes, + RequestContent, + RequestData, + RequestExtensions, + RequestFiles, + SyncByteStream, + TimeoutTypes, +) +from ._urls import URL, QueryParams +from ._utils import URLPattern, get_environment_proxies + +if typing.TYPE_CHECKING: + import ssl # pragma: no cover + +__all__ = ["USE_CLIENT_DEFAULT", "AsyncClient", "Client"] + +# The type annotation for @classmethod and context managers here follows PEP 484 +# https://www.python.org/dev/peps/pep-0484/#annotating-instance-and-class-methods +T = typing.TypeVar("T", bound="Client") +U = typing.TypeVar("U", bound="AsyncClient") + + +def _is_https_redirect(url: URL, location: URL) -> bool: + """ + Return 'True' if 'location' is a HTTPS upgrade of 'url' + """ + if url.host != location.host: + return False + + return ( + url.scheme == "http" + and _port_or_default(url) == 80 + and location.scheme == "https" + and _port_or_default(location) == 443 + ) + + +def _port_or_default(url: URL) -> int | None: + if url.port is not None: + return url.port + return {"http": 80, "https": 443}.get(url.scheme) + + +def _same_origin(url: URL, other: URL) -> bool: + """ + Return 'True' if the given URLs share the same origin. + """ + return ( + url.scheme == other.scheme + and url.host == other.host + and _port_or_default(url) == _port_or_default(other) + ) + + +class UseClientDefault: + """ + For some parameters such as `auth=...` and `timeout=...` we need to be able + to indicate the default "unset" state, in a way that is distinctly different + to using `None`. + + The default "unset" state indicates that whatever default is set on the + client should be used. This is different to setting `None`, which + explicitly disables the parameter, possibly overriding a client default. + + For example we use `timeout=USE_CLIENT_DEFAULT` in the `request()` signature. + Omitting the `timeout` parameter will send a request using whatever default + timeout has been configured on the client. Including `timeout=None` will + ensure no timeout is used. + + Note that user code shouldn't need to use the `USE_CLIENT_DEFAULT` constant, + but it is used internally when a parameter is not included. + """ + + +USE_CLIENT_DEFAULT = UseClientDefault() + + +logger = logging.getLogger("httpx") + +USER_AGENT = f"python-httpx/{__version__}" +ACCEPT_ENCODING = ", ".join( + [key for key in SUPPORTED_DECODERS.keys() if key != "identity"] +) + + +class ClientState(enum.Enum): + # UNOPENED: + # The client has been instantiated, but has not been used to send a request, + # or been opened by entering the context of a `with` block. + UNOPENED = 1 + # OPENED: + # The client has either sent a request, or is within a `with` block. + OPENED = 2 + # CLOSED: + # The client has either exited the `with` block, or `close()` has + # been called explicitly. + CLOSED = 3 + + +class BoundSyncStream(SyncByteStream): + """ + A byte stream that is bound to a given response instance, and that + ensures the `response.elapsed` is set once the response is closed. + """ + + def __init__( + self, stream: SyncByteStream, response: Response, start: float + ) -> None: + self._stream = stream + self._response = response + self._start = start + + def __iter__(self) -> typing.Iterator[bytes]: + for chunk in self._stream: + yield chunk + + def close(self) -> None: + elapsed = time.perf_counter() - self._start + self._response.elapsed = datetime.timedelta(seconds=elapsed) + self._stream.close() + + +class BoundAsyncStream(AsyncByteStream): + """ + An async byte stream that is bound to a given response instance, and that + ensures the `response.elapsed` is set once the response is closed. + """ + + def __init__( + self, stream: AsyncByteStream, response: Response, start: float + ) -> None: + self._stream = stream + self._response = response + self._start = start + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + async for chunk in self._stream: + yield chunk + + async def aclose(self) -> None: + elapsed = time.perf_counter() - self._start + self._response.elapsed = datetime.timedelta(seconds=elapsed) + await self._stream.aclose() + + +EventHook = typing.Callable[..., typing.Any] + + +class BaseClient: + def __init__( + self, + *, + auth: AuthTypes | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + max_redirects: int = DEFAULT_MAX_REDIRECTS, + event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None, + base_url: URL | str = "", + trust_env: bool = True, + default_encoding: str | typing.Callable[[bytes], str] = "utf-8", + ) -> None: + event_hooks = {} if event_hooks is None else event_hooks + + self._base_url = self._enforce_trailing_slash(URL(base_url)) + + self._auth = self._build_auth(auth) + self._params = QueryParams(params) + self.headers = Headers(headers) + self._cookies = Cookies(cookies) + self._timeout = Timeout(timeout) + self.follow_redirects = follow_redirects + self.max_redirects = max_redirects + self._event_hooks = { + "request": list(event_hooks.get("request", [])), + "response": list(event_hooks.get("response", [])), + } + self._trust_env = trust_env + self._default_encoding = default_encoding + self._state = ClientState.UNOPENED + + @property + def is_closed(self) -> bool: + """ + Check if the client being closed + """ + return self._state == ClientState.CLOSED + + @property + def trust_env(self) -> bool: + return self._trust_env + + def _enforce_trailing_slash(self, url: URL) -> URL: + if url.raw_path.endswith(b"/"): + return url + return url.copy_with(raw_path=url.raw_path + b"/") + + def _get_proxy_map( + self, proxy: ProxyTypes | None, allow_env_proxies: bool + ) -> dict[str, Proxy | None]: + if proxy is None: + if allow_env_proxies: + return { + key: None if url is None else Proxy(url=url) + for key, url in get_environment_proxies().items() + } + return {} + else: + proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy + return {"all://": proxy} + + @property + def timeout(self) -> Timeout: + return self._timeout + + @timeout.setter + def timeout(self, timeout: TimeoutTypes) -> None: + self._timeout = Timeout(timeout) + + @property + def event_hooks(self) -> dict[str, list[EventHook]]: + return self._event_hooks + + @event_hooks.setter + def event_hooks(self, event_hooks: dict[str, list[EventHook]]) -> None: + self._event_hooks = { + "request": list(event_hooks.get("request", [])), + "response": list(event_hooks.get("response", [])), + } + + @property + def auth(self) -> Auth | None: + """ + Authentication class used when none is passed at the request-level. + + See also [Authentication][0]. + + [0]: /quickstart/#authentication + """ + return self._auth + + @auth.setter + def auth(self, auth: AuthTypes) -> None: + self._auth = self._build_auth(auth) + + @property + def base_url(self) -> URL: + """ + Base URL to use when sending requests with relative URLs. + """ + return self._base_url + + @base_url.setter + def base_url(self, url: URL | str) -> None: + self._base_url = self._enforce_trailing_slash(URL(url)) + + @property + def headers(self) -> Headers: + """ + HTTP headers to include when sending requests. + """ + return self._headers + + @headers.setter + def headers(self, headers: HeaderTypes) -> None: + client_headers = Headers( + { + b"Accept": b"*/*", + b"Accept-Encoding": ACCEPT_ENCODING.encode("ascii"), + b"Connection": b"keep-alive", + b"User-Agent": USER_AGENT.encode("ascii"), + } + ) + client_headers.update(headers) + self._headers = client_headers + + @property + def cookies(self) -> Cookies: + """ + Cookie values to include when sending requests. + """ + return self._cookies + + @cookies.setter + def cookies(self, cookies: CookieTypes) -> None: + self._cookies = Cookies(cookies) + + @property + def params(self) -> QueryParams: + """ + Query parameters to include in the URL when sending requests. + """ + return self._params + + @params.setter + def params(self, params: QueryParamTypes) -> None: + self._params = QueryParams(params) + + def build_request( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Request: + """ + Build and return a request instance. + + * The `params`, `headers` and `cookies` arguments + are merged with any values set on the client. + * The `url` argument is merged with any `base_url` set on the client. + + See also: [Request instances][0] + + [0]: /advanced/clients/#request-instances + """ + url = self._merge_url(url) + headers = self._merge_headers(headers) + cookies = self._merge_cookies(cookies) + params = self._merge_queryparams(params) + extensions = {} if extensions is None else extensions + if "timeout" not in extensions: + timeout = ( + self.timeout + if isinstance(timeout, UseClientDefault) + else Timeout(timeout) + ) + extensions = dict(**extensions, timeout=timeout.as_dict()) + return Request( + method, + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + extensions=extensions, + ) + + def _merge_url(self, url: URL | str) -> URL: + """ + Merge a URL argument together with any 'base_url' on the client, + to create the URL used for the outgoing request. + """ + merge_url = URL(url) + if merge_url.is_relative_url: + # To merge URLs we always append to the base URL. To get this + # behaviour correct we always ensure the base URL ends in a '/' + # separator, and strip any leading '/' from the merge URL. + # + # So, eg... + # + # >>> client = Client(base_url="https://www.example.com/subpath") + # >>> client.base_url + # URL('https://www.example.com/subpath/') + # >>> client.build_request("GET", "/path").url + # URL('https://www.example.com/subpath/path') + merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/") + return self.base_url.copy_with(raw_path=merge_raw_path) + return merge_url + + def _merge_cookies(self, cookies: CookieTypes | None = None) -> CookieTypes | None: + """ + Merge a cookies argument together with any cookies on the client, + to create the cookies used for the outgoing request. + """ + if cookies or self.cookies: + merged_cookies = Cookies(self.cookies) + merged_cookies.update(cookies) + return merged_cookies + return cookies + + def _merge_headers(self, headers: HeaderTypes | None = None) -> HeaderTypes | None: + """ + Merge a headers argument together with any headers on the client, + to create the headers used for the outgoing request. + """ + merged_headers = Headers(self.headers) + merged_headers.update(headers) + return merged_headers + + def _merge_queryparams( + self, params: QueryParamTypes | None = None + ) -> QueryParamTypes | None: + """ + Merge a queryparams argument together with any queryparams on the client, + to create the queryparams used for the outgoing request. + """ + if params or self.params: + merged_queryparams = QueryParams(self.params) + return merged_queryparams.merge(params) + return params + + def _build_auth(self, auth: AuthTypes | None) -> Auth | None: + if auth is None: + return None + elif isinstance(auth, tuple): + return BasicAuth(username=auth[0], password=auth[1]) + elif isinstance(auth, Auth): + return auth + elif callable(auth): + return FunctionAuth(func=auth) + else: + raise TypeError(f'Invalid "auth" argument: {auth!r}') + + def _build_request_auth( + self, + request: Request, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + ) -> Auth: + auth = ( + self._auth if isinstance(auth, UseClientDefault) else self._build_auth(auth) + ) + + if auth is not None: + return auth + + username, password = request.url.username, request.url.password + if username or password: + return BasicAuth(username=username, password=password) + + return Auth() + + def _build_redirect_request(self, request: Request, response: Response) -> Request: + """ + Given a request and a redirect response, return a new request that + should be used to effect the redirect. + """ + method = self._redirect_method(request, response) + url = self._redirect_url(request, response) + headers = self._redirect_headers(request, url, method) + stream = self._redirect_stream(request, method) + cookies = Cookies(self.cookies) + return Request( + method=method, + url=url, + headers=headers, + cookies=cookies, + stream=stream, + extensions=request.extensions, + ) + + def _redirect_method(self, request: Request, response: Response) -> str: + """ + When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.SEE_OTHER and method != "HEAD": + method = "GET" + + # Do what the browsers do, despite standards... + # Turn 302s into GETs. + if response.status_code == codes.FOUND and method != "HEAD": + method = "GET" + + # If a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in 'requests' issue 1704. + if response.status_code == codes.MOVED_PERMANENTLY and method == "POST": + method = "GET" + + return method + + def _redirect_url(self, request: Request, response: Response) -> URL: + """ + Return the URL for the redirect to follow. + """ + location = response.headers["Location"] + + try: + url = URL(location) + except InvalidURL as exc: + raise RemoteProtocolError( + f"Invalid URL in location header: {exc}.", request=request + ) from None + + # Handle malformed 'Location' headers that are "absolute" form, have no host. + # See: https://github.com/encode/httpx/issues/771 + if url.scheme and not url.host: + url = url.copy_with(host=request.url.host) + + # Facilitate relative 'Location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + if url.is_relative_url: + url = request.url.join(url) + + # Attach previous fragment if needed (RFC 7231 7.1.2) + if request.url.fragment and not url.fragment: + url = url.copy_with(fragment=request.url.fragment) + + return url + + def _redirect_headers(self, request: Request, url: URL, method: str) -> Headers: + """ + Return the headers that should be used for the redirect request. + """ + headers = Headers(request.headers) + + if not _same_origin(url, request.url): + if not _is_https_redirect(request.url, url): + # Strip Authorization headers when responses are redirected + # away from the origin. (Except for direct HTTP to HTTPS redirects.) + headers.pop("Authorization", None) + + # Update the Host header. + headers["Host"] = url.netloc.decode("ascii") + + if method != request.method and method == "GET": + # If we've switch to a 'GET' request, then strip any headers which + # are only relevant to the request body. + headers.pop("Content-Length", None) + headers.pop("Transfer-Encoding", None) + + # We should use the client cookie store to determine any cookie header, + # rather than whatever was on the original outgoing request. + headers.pop("Cookie", None) + + return headers + + def _redirect_stream( + self, request: Request, method: str + ) -> SyncByteStream | AsyncByteStream | None: + """ + Return the body that should be used for the redirect request. + """ + if method != request.method and method == "GET": + return None + + return request.stream + + def _set_timeout(self, request: Request) -> None: + if "timeout" not in request.extensions: + timeout = ( + self.timeout + if isinstance(self.timeout, UseClientDefault) + else Timeout(self.timeout) + ) + request.extensions = dict(**request.extensions, timeout=timeout.as_dict()) + + +class Client(BaseClient): + """ + An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc. + + It can be shared between threads. + + Usage: + + ```python + >>> client = httpx.Client() + >>> response = client.get('https://example.org') + ``` + + **Parameters:** + + * **auth** - *(optional)* An authentication class to use when sending + requests. + * **params** - *(optional)* Query parameters to include in request URLs, as + a string, dictionary, or sequence of two-tuples. + * **headers** - *(optional)* Dictionary of HTTP headers to include when + sending requests. + * **cookies** - *(optional)* Dictionary of Cookie items to include when + sending requests. + * **verify** - *(optional)* Either `True` to use an SSL context with the + default CA bundle, `False` to disable verification, or an instance of + `ssl.SSLContext` to use a custom context. + * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be + enabled. Defaults to `False`. + * **proxy** - *(optional)* A proxy URL where all the traffic should be routed. + * **timeout** - *(optional)* The timeout configuration to use when sending + requests. + * **limits** - *(optional)* The limits configuration to use. + * **max_redirects** - *(optional)* The maximum number of redirect responses + that should be followed. + * **base_url** - *(optional)* A URL to use as the base when building + request URLs. + * **transport** - *(optional)* A transport class to use for sending requests + over the network. + * **trust_env** - *(optional)* Enables or disables usage of environment + variables for configuration. + * **default_encoding** - *(optional)* The default encoding to use for decoding + response text, if no charset information is included in a response Content-Type + header. Set to a callable for automatic character set detection. Default: "utf-8". + """ + + def __init__( + self, + *, + auth: AuthTypes | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + proxy: ProxyTypes | None = None, + mounts: None | (typing.Mapping[str, BaseTransport | None]) = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + limits: Limits = DEFAULT_LIMITS, + max_redirects: int = DEFAULT_MAX_REDIRECTS, + event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None, + base_url: URL | str = "", + transport: BaseTransport | None = None, + default_encoding: str | typing.Callable[[bytes], str] = "utf-8", + ) -> None: + super().__init__( + auth=auth, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + follow_redirects=follow_redirects, + max_redirects=max_redirects, + event_hooks=event_hooks, + base_url=base_url, + trust_env=trust_env, + default_encoding=default_encoding, + ) + + if http2: + try: + import h2 # noqa + except ImportError: # pragma: no cover + raise ImportError( + "Using http2=True, but the 'h2' package is not installed. " + "Make sure to install httpx using `pip install httpx[http2]`." + ) from None + + allow_env_proxies = trust_env and transport is None + proxy_map = self._get_proxy_map(proxy, allow_env_proxies) + + self._transport = self._init_transport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + transport=transport, + ) + self._mounts: dict[URLPattern, BaseTransport | None] = { + URLPattern(key): None + if proxy is None + else self._init_proxy_transport( + proxy, + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + for key, proxy in proxy_map.items() + } + if mounts is not None: + self._mounts.update( + {URLPattern(key): transport for key, transport in mounts.items()} + ) + + self._mounts = dict(sorted(self._mounts.items())) + + def _init_transport( + self, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + transport: BaseTransport | None = None, + ) -> BaseTransport: + if transport is not None: + return transport + + return HTTPTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + + def _init_proxy_transport( + self, + proxy: Proxy, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + ) -> BaseTransport: + return HTTPTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + proxy=proxy, + ) + + def _transport_for_url(self, url: URL) -> BaseTransport: + """ + Returns the transport instance that should be used for a given URL. + This will either be the standard connection pool, or a proxy. + """ + for pattern, transport in self._mounts.items(): + if pattern.matches(url): + return self._transport if transport is None else transport + + return self._transport + + def request( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Build and send a request. + + Equivalent to: + + ```python + request = client.build_request(...) + response = client.send(request, ...) + ``` + + See `Client.build_request()`, `Client.send()` and + [Merging of configuration][0] for how the various parameters + are merged with client-level configuration. + + [0]: /advanced/clients/#merging-of-configuration + """ + if cookies is not None: + message = ( + "Setting per-request cookies=<...> is being deprecated, because " + "the expected behaviour on cookie persistence is ambiguous. Set " + "cookies directly on the client instance instead." + ) + warnings.warn(message, DeprecationWarning, stacklevel=2) + + request = self.build_request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + return self.send(request, auth=auth, follow_redirects=follow_redirects) + + @contextmanager + def stream( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> typing.Iterator[Response]: + """ + Alternative to `httpx.request()` that streams the response body + instead of loading it into memory at once. + + **Parameters**: See `httpx.request`. + + See also: [Streaming Responses][0] + + [0]: /quickstart#streaming-responses + """ + request = self.build_request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + response = self.send( + request=request, + auth=auth, + follow_redirects=follow_redirects, + stream=True, + ) + try: + yield response + finally: + response.close() + + def send( + self, + request: Request, + *, + stream: bool = False, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + ) -> Response: + """ + Send a request. + + The request is sent as-is, unmodified. + + Typically you'll want to build one with `Client.build_request()` + so that any client-level configuration is merged into the request, + but passing an explicit `httpx.Request()` is supported as well. + + See also: [Request instances][0] + + [0]: /advanced/clients/#request-instances + """ + if self._state == ClientState.CLOSED: + raise RuntimeError("Cannot send a request, as the client has been closed.") + + self._state = ClientState.OPENED + follow_redirects = ( + self.follow_redirects + if isinstance(follow_redirects, UseClientDefault) + else follow_redirects + ) + + self._set_timeout(request) + + auth = self._build_request_auth(request, auth) + + response = self._send_handling_auth( + request, + auth=auth, + follow_redirects=follow_redirects, + history=[], + ) + try: + if not stream: + response.read() + + return response + + except BaseException as exc: + response.close() + raise exc + + def _send_handling_auth( + self, + request: Request, + auth: Auth, + follow_redirects: bool, + history: list[Response], + ) -> Response: + auth_flow = auth.sync_auth_flow(request) + try: + request = next(auth_flow) + + while True: + response = self._send_handling_redirects( + request, + follow_redirects=follow_redirects, + history=history, + ) + try: + try: + next_request = auth_flow.send(response) + except StopIteration: + return response + + response.history = list(history) + response.read() + request = next_request + history.append(response) + + except BaseException as exc: + response.close() + raise exc + finally: + auth_flow.close() + + def _send_handling_redirects( + self, + request: Request, + follow_redirects: bool, + history: list[Response], + ) -> Response: + while True: + if len(history) > self.max_redirects: + raise TooManyRedirects( + "Exceeded maximum allowed redirects.", request=request + ) + + for hook in self._event_hooks["request"]: + hook(request) + + response = self._send_single_request(request) + try: + for hook in self._event_hooks["response"]: + hook(response) + response.history = list(history) + + if not response.has_redirect_location: + return response + + request = self._build_redirect_request(request, response) + history = history + [response] + + if follow_redirects: + response.read() + else: + response.next_request = request + return response + + except BaseException as exc: + response.close() + raise exc + + def _send_single_request(self, request: Request) -> Response: + """ + Sends a single request, without handling any redirections. + """ + transport = self._transport_for_url(request.url) + start = time.perf_counter() + + if not isinstance(request.stream, SyncByteStream): + raise RuntimeError( + "Attempted to send an async request with a sync Client instance." + ) + + with request_context(request=request): + response = transport.handle_request(request) + + assert isinstance(response.stream, SyncByteStream) + + response.request = request + response.stream = BoundSyncStream( + response.stream, response=response, start=start + ) + self.cookies.extract_cookies(response) + response.default_encoding = self._default_encoding + + logger.info( + 'HTTP Request: %s %s "%s %d %s"', + request.method, + request.url, + response.http_version, + response.status_code, + response.reason_phrase, + ) + + return response + + def get( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `GET` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "GET", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def options( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send an `OPTIONS` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "OPTIONS", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def head( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `HEAD` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "HEAD", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def post( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `POST` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "POST", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def put( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `PUT` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "PUT", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def patch( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `PATCH` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "PATCH", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def delete( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `DELETE` request. + + **Parameters**: See `httpx.request`. + """ + return self.request( + "DELETE", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + def close(self) -> None: + """ + Close transport and proxies. + """ + if self._state != ClientState.CLOSED: + self._state = ClientState.CLOSED + + self._transport.close() + for transport in self._mounts.values(): + if transport is not None: + transport.close() + + def __enter__(self: T) -> T: + if self._state != ClientState.UNOPENED: + msg = { + ClientState.OPENED: "Cannot open a client instance more than once.", + ClientState.CLOSED: ( + "Cannot reopen a client instance, once it has been closed." + ), + }[self._state] + raise RuntimeError(msg) + + self._state = ClientState.OPENED + + self._transport.__enter__() + for transport in self._mounts.values(): + if transport is not None: + transport.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + self._state = ClientState.CLOSED + + self._transport.__exit__(exc_type, exc_value, traceback) + for transport in self._mounts.values(): + if transport is not None: + transport.__exit__(exc_type, exc_value, traceback) + + +class AsyncClient(BaseClient): + """ + An asynchronous HTTP client, with connection pooling, HTTP/2, redirects, + cookie persistence, etc. + + It can be shared between tasks. + + Usage: + + ```python + >>> async with httpx.AsyncClient() as client: + >>> response = await client.get('https://example.org') + ``` + + **Parameters:** + + * **auth** - *(optional)* An authentication class to use when sending + requests. + * **params** - *(optional)* Query parameters to include in request URLs, as + a string, dictionary, or sequence of two-tuples. + * **headers** - *(optional)* Dictionary of HTTP headers to include when + sending requests. + * **cookies** - *(optional)* Dictionary of Cookie items to include when + sending requests. + * **verify** - *(optional)* Either `True` to use an SSL context with the + default CA bundle, `False` to disable verification, or an instance of + `ssl.SSLContext` to use a custom context. + * **http2** - *(optional)* A boolean indicating if HTTP/2 support should be + enabled. Defaults to `False`. + * **proxy** - *(optional)* A proxy URL where all the traffic should be routed. + * **timeout** - *(optional)* The timeout configuration to use when sending + requests. + * **limits** - *(optional)* The limits configuration to use. + * **max_redirects** - *(optional)* The maximum number of redirect responses + that should be followed. + * **base_url** - *(optional)* A URL to use as the base when building + request URLs. + * **transport** - *(optional)* A transport class to use for sending requests + over the network. + * **trust_env** - *(optional)* Enables or disables usage of environment + variables for configuration. + * **default_encoding** - *(optional)* The default encoding to use for decoding + response text, if no charset information is included in a response Content-Type + header. Set to a callable for automatic character set detection. Default: "utf-8". + """ + + def __init__( + self, + *, + auth: AuthTypes | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + http1: bool = True, + http2: bool = False, + proxy: ProxyTypes | None = None, + mounts: None | (typing.Mapping[str, AsyncBaseTransport | None]) = None, + timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG, + follow_redirects: bool = False, + limits: Limits = DEFAULT_LIMITS, + max_redirects: int = DEFAULT_MAX_REDIRECTS, + event_hooks: None | (typing.Mapping[str, list[EventHook]]) = None, + base_url: URL | str = "", + transport: AsyncBaseTransport | None = None, + trust_env: bool = True, + default_encoding: str | typing.Callable[[bytes], str] = "utf-8", + ) -> None: + super().__init__( + auth=auth, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + follow_redirects=follow_redirects, + max_redirects=max_redirects, + event_hooks=event_hooks, + base_url=base_url, + trust_env=trust_env, + default_encoding=default_encoding, + ) + + if http2: + try: + import h2 # noqa + except ImportError: # pragma: no cover + raise ImportError( + "Using http2=True, but the 'h2' package is not installed. " + "Make sure to install httpx using `pip install httpx[http2]`." + ) from None + + allow_env_proxies = trust_env and transport is None + proxy_map = self._get_proxy_map(proxy, allow_env_proxies) + + self._transport = self._init_transport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + transport=transport, + ) + + self._mounts: dict[URLPattern, AsyncBaseTransport | None] = { + URLPattern(key): None + if proxy is None + else self._init_proxy_transport( + proxy, + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + for key, proxy in proxy_map.items() + } + if mounts is not None: + self._mounts.update( + {URLPattern(key): transport for key, transport in mounts.items()} + ) + self._mounts = dict(sorted(self._mounts.items())) + + def _init_transport( + self, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + transport: AsyncBaseTransport | None = None, + ) -> AsyncBaseTransport: + if transport is not None: + return transport + + return AsyncHTTPTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + ) + + def _init_proxy_transport( + self, + proxy: Proxy, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + ) -> AsyncBaseTransport: + return AsyncHTTPTransport( + verify=verify, + cert=cert, + trust_env=trust_env, + http1=http1, + http2=http2, + limits=limits, + proxy=proxy, + ) + + def _transport_for_url(self, url: URL) -> AsyncBaseTransport: + """ + Returns the transport instance that should be used for a given URL. + This will either be the standard connection pool, or a proxy. + """ + for pattern, transport in self._mounts.items(): + if pattern.matches(url): + return self._transport if transport is None else transport + + return self._transport + + async def request( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Build and send a request. + + Equivalent to: + + ```python + request = client.build_request(...) + response = await client.send(request, ...) + ``` + + See `AsyncClient.build_request()`, `AsyncClient.send()` + and [Merging of configuration][0] for how the various parameters + are merged with client-level configuration. + + [0]: /advanced/clients/#merging-of-configuration + """ + + if cookies is not None: # pragma: no cover + message = ( + "Setting per-request cookies=<...> is being deprecated, because " + "the expected behaviour on cookie persistence is ambiguous. Set " + "cookies directly on the client instance instead." + ) + warnings.warn(message, DeprecationWarning, stacklevel=2) + + request = self.build_request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + return await self.send(request, auth=auth, follow_redirects=follow_redirects) + + @asynccontextmanager + async def stream( + self, + method: str, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> typing.AsyncIterator[Response]: + """ + Alternative to `httpx.request()` that streams the response body + instead of loading it into memory at once. + + **Parameters**: See `httpx.request`. + + See also: [Streaming Responses][0] + + [0]: /quickstart#streaming-responses + """ + request = self.build_request( + method=method, + url=url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + timeout=timeout, + extensions=extensions, + ) + response = await self.send( + request=request, + auth=auth, + follow_redirects=follow_redirects, + stream=True, + ) + try: + yield response + finally: + await response.aclose() + + async def send( + self, + request: Request, + *, + stream: bool = False, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + ) -> Response: + """ + Send a request. + + The request is sent as-is, unmodified. + + Typically you'll want to build one with `AsyncClient.build_request()` + so that any client-level configuration is merged into the request, + but passing an explicit `httpx.Request()` is supported as well. + + See also: [Request instances][0] + + [0]: /advanced/clients/#request-instances + """ + if self._state == ClientState.CLOSED: + raise RuntimeError("Cannot send a request, as the client has been closed.") + + self._state = ClientState.OPENED + follow_redirects = ( + self.follow_redirects + if isinstance(follow_redirects, UseClientDefault) + else follow_redirects + ) + + self._set_timeout(request) + + auth = self._build_request_auth(request, auth) + + response = await self._send_handling_auth( + request, + auth=auth, + follow_redirects=follow_redirects, + history=[], + ) + try: + if not stream: + await response.aread() + + return response + + except BaseException as exc: + await response.aclose() + raise exc + + async def _send_handling_auth( + self, + request: Request, + auth: Auth, + follow_redirects: bool, + history: list[Response], + ) -> Response: + auth_flow = auth.async_auth_flow(request) + try: + request = await auth_flow.__anext__() + + while True: + response = await self._send_handling_redirects( + request, + follow_redirects=follow_redirects, + history=history, + ) + try: + try: + next_request = await auth_flow.asend(response) + except StopAsyncIteration: + return response + + response.history = list(history) + await response.aread() + request = next_request + history.append(response) + + except BaseException as exc: + await response.aclose() + raise exc + finally: + await auth_flow.aclose() + + async def _send_handling_redirects( + self, + request: Request, + follow_redirects: bool, + history: list[Response], + ) -> Response: + while True: + if len(history) > self.max_redirects: + raise TooManyRedirects( + "Exceeded maximum allowed redirects.", request=request + ) + + for hook in self._event_hooks["request"]: + await hook(request) + + response = await self._send_single_request(request) + try: + for hook in self._event_hooks["response"]: + await hook(response) + + response.history = list(history) + + if not response.has_redirect_location: + return response + + request = self._build_redirect_request(request, response) + history = history + [response] + + if follow_redirects: + await response.aread() + else: + response.next_request = request + return response + + except BaseException as exc: + await response.aclose() + raise exc + + async def _send_single_request(self, request: Request) -> Response: + """ + Sends a single request, without handling any redirections. + """ + transport = self._transport_for_url(request.url) + start = time.perf_counter() + + if not isinstance(request.stream, AsyncByteStream): + raise RuntimeError( + "Attempted to send an sync request with an AsyncClient instance." + ) + + with request_context(request=request): + response = await transport.handle_async_request(request) + + assert isinstance(response.stream, AsyncByteStream) + response.request = request + response.stream = BoundAsyncStream( + response.stream, response=response, start=start + ) + self.cookies.extract_cookies(response) + response.default_encoding = self._default_encoding + + logger.info( + 'HTTP Request: %s %s "%s %d %s"', + request.method, + request.url, + response.http_version, + response.status_code, + response.reason_phrase, + ) + + return response + + async def get( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `GET` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "GET", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def options( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send an `OPTIONS` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "OPTIONS", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def head( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `HEAD` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "HEAD", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def post( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `POST` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "POST", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def put( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `PUT` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "PUT", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def patch( + self, + url: URL | str, + *, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `PATCH` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "PATCH", + url, + content=content, + data=data, + files=files, + json=json, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def delete( + self, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, + follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: RequestExtensions | None = None, + ) -> Response: + """ + Send a `DELETE` request. + + **Parameters**: See `httpx.request`. + """ + return await self.request( + "DELETE", + url, + params=params, + headers=headers, + cookies=cookies, + auth=auth, + follow_redirects=follow_redirects, + timeout=timeout, + extensions=extensions, + ) + + async def aclose(self) -> None: + """ + Close transport and proxies. + """ + if self._state != ClientState.CLOSED: + self._state = ClientState.CLOSED + + await self._transport.aclose() + for proxy in self._mounts.values(): + if proxy is not None: + await proxy.aclose() + + async def __aenter__(self: U) -> U: + if self._state != ClientState.UNOPENED: + msg = { + ClientState.OPENED: "Cannot open a client instance more than once.", + ClientState.CLOSED: ( + "Cannot reopen a client instance, once it has been closed." + ), + }[self._state] + raise RuntimeError(msg) + + self._state = ClientState.OPENED + + await self._transport.__aenter__() + for proxy in self._mounts.values(): + if proxy is not None: + await proxy.__aenter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + self._state = ClientState.CLOSED + + await self._transport.__aexit__(exc_type, exc_value, traceback) + for proxy in self._mounts.values(): + if proxy is not None: + await proxy.__aexit__(exc_type, exc_value, traceback) diff --git a/.venv/Lib/site-packages/httpx/_config.py b/.venv/Lib/site-packages/httpx/_config.py new file mode 100644 index 0000000..467a6c9 --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_config.py @@ -0,0 +1,248 @@ +from __future__ import annotations + +import os +import typing + +from ._models import Headers +from ._types import CertTypes, HeaderTypes, TimeoutTypes +from ._urls import URL + +if typing.TYPE_CHECKING: + import ssl # pragma: no cover + +__all__ = ["Limits", "Proxy", "Timeout", "create_ssl_context"] + + +class UnsetType: + pass # pragma: no cover + + +UNSET = UnsetType() + + +def create_ssl_context( + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, +) -> ssl.SSLContext: + import ssl + import warnings + + import certifi + + if verify is True: + if trust_env and os.environ.get("SSL_CERT_FILE"): # pragma: nocover + ctx = ssl.create_default_context(cafile=os.environ["SSL_CERT_FILE"]) + elif trust_env and os.environ.get("SSL_CERT_DIR"): # pragma: nocover + ctx = ssl.create_default_context(capath=os.environ["SSL_CERT_DIR"]) + else: + # Default case... + ctx = ssl.create_default_context(cafile=certifi.where()) + elif verify is False: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + elif isinstance(verify, str): # pragma: nocover + message = ( + "`verify=` is deprecated. " + "Use `verify=ssl.create_default_context(cafile=...)` " + "or `verify=ssl.create_default_context(capath=...)` instead." + ) + warnings.warn(message, DeprecationWarning) + if os.path.isdir(verify): + return ssl.create_default_context(capath=verify) + return ssl.create_default_context(cafile=verify) + else: + ctx = verify + + if cert: # pragma: nocover + message = ( + "`cert=...` is deprecated. Use `verify=` instead," + "with `.load_cert_chain()` to configure the certificate chain." + ) + warnings.warn(message, DeprecationWarning) + if isinstance(cert, str): + ctx.load_cert_chain(cert) + else: + ctx.load_cert_chain(*cert) + + return ctx + + +class Timeout: + """ + Timeout configuration. + + **Usage**: + + Timeout(None) # No timeouts. + Timeout(5.0) # 5s timeout on all operations. + Timeout(None, connect=5.0) # 5s timeout on connect, no other timeouts. + Timeout(5.0, connect=10.0) # 10s timeout on connect. 5s timeout elsewhere. + Timeout(5.0, pool=None) # No timeout on acquiring connection from pool. + # 5s timeout elsewhere. + """ + + def __init__( + self, + timeout: TimeoutTypes | UnsetType = UNSET, + *, + connect: None | float | UnsetType = UNSET, + read: None | float | UnsetType = UNSET, + write: None | float | UnsetType = UNSET, + pool: None | float | UnsetType = UNSET, + ) -> None: + if isinstance(timeout, Timeout): + # Passed as a single explicit Timeout. + assert connect is UNSET + assert read is UNSET + assert write is UNSET + assert pool is UNSET + self.connect = timeout.connect # type: typing.Optional[float] + self.read = timeout.read # type: typing.Optional[float] + self.write = timeout.write # type: typing.Optional[float] + self.pool = timeout.pool # type: typing.Optional[float] + elif isinstance(timeout, tuple): + # Passed as a tuple. + self.connect = timeout[0] + self.read = timeout[1] + self.write = None if len(timeout) < 3 else timeout[2] + self.pool = None if len(timeout) < 4 else timeout[3] + elif not ( + isinstance(connect, UnsetType) + or isinstance(read, UnsetType) + or isinstance(write, UnsetType) + or isinstance(pool, UnsetType) + ): + self.connect = connect + self.read = read + self.write = write + self.pool = pool + else: + if isinstance(timeout, UnsetType): + raise ValueError( + "httpx.Timeout must either include a default, or set all " + "four parameters explicitly." + ) + self.connect = timeout if isinstance(connect, UnsetType) else connect + self.read = timeout if isinstance(read, UnsetType) else read + self.write = timeout if isinstance(write, UnsetType) else write + self.pool = timeout if isinstance(pool, UnsetType) else pool + + def as_dict(self) -> dict[str, float | None]: + return { + "connect": self.connect, + "read": self.read, + "write": self.write, + "pool": self.pool, + } + + def __eq__(self, other: typing.Any) -> bool: + return ( + isinstance(other, self.__class__) + and self.connect == other.connect + and self.read == other.read + and self.write == other.write + and self.pool == other.pool + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + if len({self.connect, self.read, self.write, self.pool}) == 1: + return f"{class_name}(timeout={self.connect})" + return ( + f"{class_name}(connect={self.connect}, " + f"read={self.read}, write={self.write}, pool={self.pool})" + ) + + +class Limits: + """ + Configuration for limits to various client behaviors. + + **Parameters:** + + * **max_connections** - The maximum number of concurrent connections that may be + established. + * **max_keepalive_connections** - Allow the connection pool to maintain + keep-alive connections below this point. Should be less than or equal + to `max_connections`. + * **keepalive_expiry** - Time limit on idle keep-alive connections in seconds. + """ + + def __init__( + self, + *, + max_connections: int | None = None, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = 5.0, + ) -> None: + self.max_connections = max_connections + self.max_keepalive_connections = max_keepalive_connections + self.keepalive_expiry = keepalive_expiry + + def __eq__(self, other: typing.Any) -> bool: + return ( + isinstance(other, self.__class__) + and self.max_connections == other.max_connections + and self.max_keepalive_connections == other.max_keepalive_connections + and self.keepalive_expiry == other.keepalive_expiry + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + return ( + f"{class_name}(max_connections={self.max_connections}, " + f"max_keepalive_connections={self.max_keepalive_connections}, " + f"keepalive_expiry={self.keepalive_expiry})" + ) + + +class Proxy: + def __init__( + self, + url: URL | str, + *, + ssl_context: ssl.SSLContext | None = None, + auth: tuple[str, str] | None = None, + headers: HeaderTypes | None = None, + ) -> None: + url = URL(url) + headers = Headers(headers) + + if url.scheme not in ("http", "https", "socks5", "socks5h"): + raise ValueError(f"Unknown scheme for proxy URL {url!r}") + + if url.username or url.password: + # Remove any auth credentials from the URL. + auth = (url.username, url.password) + url = url.copy_with(username=None, password=None) + + self.url = url + self.auth = auth + self.headers = headers + self.ssl_context = ssl_context + + @property + def raw_auth(self) -> tuple[bytes, bytes] | None: + # The proxy authentication as raw bytes. + return ( + None + if self.auth is None + else (self.auth[0].encode("utf-8"), self.auth[1].encode("utf-8")) + ) + + def __repr__(self) -> str: + # The authentication is represented with the password component masked. + auth = (self.auth[0], "********") if self.auth else None + + # Build a nice concise representation. + url_str = f"{str(self.url)!r}" + auth_str = f", auth={auth!r}" if auth else "" + headers_str = f", headers={dict(self.headers)!r}" if self.headers else "" + return f"Proxy({url_str}{auth_str}{headers_str})" + + +DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0) +DEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20) +DEFAULT_MAX_REDIRECTS = 20 diff --git a/.venv/Lib/site-packages/httpx/_content.py b/.venv/Lib/site-packages/httpx/_content.py new file mode 100644 index 0000000..6f479a0 --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_content.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +import inspect +import warnings +from json import dumps as json_dumps +from typing import ( + Any, + AsyncIterable, + AsyncIterator, + Iterable, + Iterator, + Mapping, +) +from urllib.parse import urlencode + +from ._exceptions import StreamClosed, StreamConsumed +from ._multipart import MultipartStream +from ._types import ( + AsyncByteStream, + RequestContent, + RequestData, + RequestFiles, + ResponseContent, + SyncByteStream, +) +from ._utils import peek_filelike_length, primitive_value_to_str + +__all__ = ["ByteStream"] + + +class ByteStream(AsyncByteStream, SyncByteStream): + def __init__(self, stream: bytes) -> None: + self._stream = stream + + def __iter__(self) -> Iterator[bytes]: + yield self._stream + + async def __aiter__(self) -> AsyncIterator[bytes]: + yield self._stream + + +class IteratorByteStream(SyncByteStream): + CHUNK_SIZE = 65_536 + + def __init__(self, stream: Iterable[bytes]) -> None: + self._stream = stream + self._is_stream_consumed = False + self._is_generator = inspect.isgenerator(stream) + + def __iter__(self) -> Iterator[bytes]: + if self._is_stream_consumed and self._is_generator: + raise StreamConsumed() + + self._is_stream_consumed = True + if hasattr(self._stream, "read"): + # File-like interfaces should use 'read' directly. + chunk = self._stream.read(self.CHUNK_SIZE) + while chunk: + yield chunk + chunk = self._stream.read(self.CHUNK_SIZE) + else: + # Otherwise iterate. + for part in self._stream: + yield part + + +class AsyncIteratorByteStream(AsyncByteStream): + CHUNK_SIZE = 65_536 + + def __init__(self, stream: AsyncIterable[bytes]) -> None: + self._stream = stream + self._is_stream_consumed = False + self._is_generator = inspect.isasyncgen(stream) + + async def __aiter__(self) -> AsyncIterator[bytes]: + if self._is_stream_consumed and self._is_generator: + raise StreamConsumed() + + self._is_stream_consumed = True + if hasattr(self._stream, "aread"): + # File-like interfaces should use 'aread' directly. + chunk = await self._stream.aread(self.CHUNK_SIZE) + while chunk: + yield chunk + chunk = await self._stream.aread(self.CHUNK_SIZE) + else: + # Otherwise iterate. + async for part in self._stream: + yield part + + +class UnattachedStream(AsyncByteStream, SyncByteStream): + """ + If a request or response is serialized using pickle, then it is no longer + attached to a stream for I/O purposes. Any stream operations should result + in `httpx.StreamClosed`. + """ + + def __iter__(self) -> Iterator[bytes]: + raise StreamClosed() + + async def __aiter__(self) -> AsyncIterator[bytes]: + raise StreamClosed() + yield b"" # pragma: no cover + + +def encode_content( + content: str | bytes | Iterable[bytes] | AsyncIterable[bytes], +) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]: + if isinstance(content, (bytes, str)): + body = content.encode("utf-8") if isinstance(content, str) else content + content_length = len(body) + headers = {"Content-Length": str(content_length)} if body else {} + return headers, ByteStream(body) + + elif isinstance(content, Iterable) and not isinstance(content, dict): + # `not isinstance(content, dict)` is a bit oddly specific, but it + # catches a case that's easy for users to make in error, and would + # otherwise pass through here, like any other bytes-iterable, + # because `dict` happens to be iterable. See issue #2491. + content_length_or_none = peek_filelike_length(content) + + if content_length_or_none is None: + headers = {"Transfer-Encoding": "chunked"} + else: + headers = {"Content-Length": str(content_length_or_none)} + return headers, IteratorByteStream(content) # type: ignore + + elif isinstance(content, AsyncIterable): + headers = {"Transfer-Encoding": "chunked"} + return headers, AsyncIteratorByteStream(content) + + raise TypeError(f"Unexpected type for 'content', {type(content)!r}") + + +def encode_urlencoded_data( + data: RequestData, +) -> tuple[dict[str, str], ByteStream]: + plain_data = [] + for key, value in data.items(): + if isinstance(value, (list, tuple)): + plain_data.extend([(key, primitive_value_to_str(item)) for item in value]) + else: + plain_data.append((key, primitive_value_to_str(value))) + body = urlencode(plain_data, doseq=True).encode("utf-8") + content_length = str(len(body)) + content_type = "application/x-www-form-urlencoded" + headers = {"Content-Length": content_length, "Content-Type": content_type} + return headers, ByteStream(body) + + +def encode_multipart_data( + data: RequestData, files: RequestFiles, boundary: bytes | None +) -> tuple[dict[str, str], MultipartStream]: + multipart = MultipartStream(data=data, files=files, boundary=boundary) + headers = multipart.get_headers() + return headers, multipart + + +def encode_text(text: str) -> tuple[dict[str, str], ByteStream]: + body = text.encode("utf-8") + content_length = str(len(body)) + content_type = "text/plain; charset=utf-8" + headers = {"Content-Length": content_length, "Content-Type": content_type} + return headers, ByteStream(body) + + +def encode_html(html: str) -> tuple[dict[str, str], ByteStream]: + body = html.encode("utf-8") + content_length = str(len(body)) + content_type = "text/html; charset=utf-8" + headers = {"Content-Length": content_length, "Content-Type": content_type} + return headers, ByteStream(body) + + +def encode_json(json: Any) -> tuple[dict[str, str], ByteStream]: + body = json_dumps( + json, ensure_ascii=False, separators=(",", ":"), allow_nan=False + ).encode("utf-8") + content_length = str(len(body)) + content_type = "application/json" + headers = {"Content-Length": content_length, "Content-Type": content_type} + return headers, ByteStream(body) + + +def encode_request( + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: Any | None = None, + boundary: bytes | None = None, +) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]: + """ + Handles encoding the given `content`, `data`, `files`, and `json`, + returning a two-tuple of (, ). + """ + if data is not None and not isinstance(data, Mapping): + # We prefer to separate `content=` + # for raw request content, and `data=
` for url encoded or + # multipart form content. + # + # However for compat with requests, we *do* still support + # `data=` usages. We deal with that case here, treating it + # as if `content=<...>` had been supplied instead. + message = "Use 'content=<...>' to upload raw bytes/text content." + warnings.warn(message, DeprecationWarning, stacklevel=2) + return encode_content(data) + + if content is not None: + return encode_content(content) + elif files: + return encode_multipart_data(data or {}, files, boundary) + elif data: + return encode_urlencoded_data(data) + elif json is not None: + return encode_json(json) + + return {}, ByteStream(b"") + + +def encode_response( + content: ResponseContent | None = None, + text: str | None = None, + html: str | None = None, + json: Any | None = None, +) -> tuple[dict[str, str], SyncByteStream | AsyncByteStream]: + """ + Handles encoding the given `content`, returning a two-tuple of + (, ). + """ + if content is not None: + return encode_content(content) + elif text is not None: + return encode_text(text) + elif html is not None: + return encode_html(html) + elif json is not None: + return encode_json(json) + + return {}, ByteStream(b"") diff --git a/.venv/Lib/site-packages/httpx/_decoders.py b/.venv/Lib/site-packages/httpx/_decoders.py new file mode 100644 index 0000000..899dfad --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_decoders.py @@ -0,0 +1,393 @@ +""" +Handlers for Content-Encoding. + +See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding +""" + +from __future__ import annotations + +import codecs +import io +import typing +import zlib + +from ._exceptions import DecodingError + +# Brotli support is optional +try: + # The C bindings in `brotli` are recommended for CPython. + import brotli +except ImportError: # pragma: no cover + try: + # The CFFI bindings in `brotlicffi` are recommended for PyPy + # and other environments. + import brotlicffi as brotli + except ImportError: + brotli = None + + +# Zstandard support is optional +try: + import zstandard +except ImportError: # pragma: no cover + zstandard = None # type: ignore + + +class ContentDecoder: + def decode(self, data: bytes) -> bytes: + raise NotImplementedError() # pragma: no cover + + def flush(self) -> bytes: + raise NotImplementedError() # pragma: no cover + + +class IdentityDecoder(ContentDecoder): + """ + Handle unencoded data. + """ + + def decode(self, data: bytes) -> bytes: + return data + + def flush(self) -> bytes: + return b"" + + +class DeflateDecoder(ContentDecoder): + """ + Handle 'deflate' decoding. + + See: https://stackoverflow.com/questions/1838699 + """ + + def __init__(self) -> None: + self.first_attempt = True + self.decompressor = zlib.decompressobj() + + def decode(self, data: bytes) -> bytes: + was_first_attempt = self.first_attempt + self.first_attempt = False + try: + return self.decompressor.decompress(data) + except zlib.error as exc: + if was_first_attempt: + self.decompressor = zlib.decompressobj(-zlib.MAX_WBITS) + return self.decode(data) + raise DecodingError(str(exc)) from exc + + def flush(self) -> bytes: + try: + return self.decompressor.flush() + except zlib.error as exc: # pragma: no cover + raise DecodingError(str(exc)) from exc + + +class GZipDecoder(ContentDecoder): + """ + Handle 'gzip' decoding. + + See: https://stackoverflow.com/questions/1838699 + """ + + def __init__(self) -> None: + self.decompressor = zlib.decompressobj(zlib.MAX_WBITS | 16) + + def decode(self, data: bytes) -> bytes: + try: + return self.decompressor.decompress(data) + except zlib.error as exc: + raise DecodingError(str(exc)) from exc + + def flush(self) -> bytes: + try: + return self.decompressor.flush() + except zlib.error as exc: # pragma: no cover + raise DecodingError(str(exc)) from exc + + +class BrotliDecoder(ContentDecoder): + """ + Handle 'brotli' decoding. + + Requires `pip install brotlipy`. See: https://brotlipy.readthedocs.io/ + or `pip install brotli`. See https://github.com/google/brotli + Supports both 'brotlipy' and 'Brotli' packages since they share an import + name. The top branches are for 'brotlipy' and bottom branches for 'Brotli' + """ + + def __init__(self) -> None: + if brotli is None: # pragma: no cover + raise ImportError( + "Using 'BrotliDecoder', but neither of the 'brotlicffi' or 'brotli' " + "packages have been installed. " + "Make sure to install httpx using `pip install httpx[brotli]`." + ) from None + + self.decompressor = brotli.Decompressor() + self.seen_data = False + self._decompress: typing.Callable[[bytes], bytes] + if hasattr(self.decompressor, "decompress"): + # The 'brotlicffi' package. + self._decompress = self.decompressor.decompress # pragma: no cover + else: + # The 'brotli' package. + self._decompress = self.decompressor.process # pragma: no cover + + def decode(self, data: bytes) -> bytes: + if not data: + return b"" + self.seen_data = True + try: + return self._decompress(data) + except brotli.error as exc: + raise DecodingError(str(exc)) from exc + + def flush(self) -> bytes: + if not self.seen_data: + return b"" + try: + if hasattr(self.decompressor, "finish"): + # Only available in the 'brotlicffi' package. + + # As the decompressor decompresses eagerly, this + # will never actually emit any data. However, it will potentially throw + # errors if a truncated or damaged data stream has been used. + self.decompressor.finish() # pragma: no cover + return b"" + except brotli.error as exc: # pragma: no cover + raise DecodingError(str(exc)) from exc + + +class ZStandardDecoder(ContentDecoder): + """ + Handle 'zstd' RFC 8878 decoding. + + Requires `pip install zstandard`. + Can be installed as a dependency of httpx using `pip install httpx[zstd]`. + """ + + # inspired by the ZstdDecoder implementation in urllib3 + def __init__(self) -> None: + if zstandard is None: # pragma: no cover + raise ImportError( + "Using 'ZStandardDecoder', ..." + "Make sure to install httpx using `pip install httpx[zstd]`." + ) from None + + self.decompressor = zstandard.ZstdDecompressor().decompressobj() + self.seen_data = False + + def decode(self, data: bytes) -> bytes: + assert zstandard is not None + self.seen_data = True + output = io.BytesIO() + try: + output.write(self.decompressor.decompress(data)) + while self.decompressor.eof and self.decompressor.unused_data: + unused_data = self.decompressor.unused_data + self.decompressor = zstandard.ZstdDecompressor().decompressobj() + output.write(self.decompressor.decompress(unused_data)) + except zstandard.ZstdError as exc: + raise DecodingError(str(exc)) from exc + return output.getvalue() + + def flush(self) -> bytes: + if not self.seen_data: + return b"" + ret = self.decompressor.flush() # note: this is a no-op + if not self.decompressor.eof: + raise DecodingError("Zstandard data is incomplete") # pragma: no cover + return bytes(ret) + + +class MultiDecoder(ContentDecoder): + """ + Handle the case where multiple encodings have been applied. + """ + + def __init__(self, children: typing.Sequence[ContentDecoder]) -> None: + """ + 'children' should be a sequence of decoders in the order in which + each was applied. + """ + # Note that we reverse the order for decoding. + self.children = list(reversed(children)) + + def decode(self, data: bytes) -> bytes: + for child in self.children: + data = child.decode(data) + return data + + def flush(self) -> bytes: + data = b"" + for child in self.children: + data = child.decode(data) + child.flush() + return data + + +class ByteChunker: + """ + Handles returning byte content in fixed-size chunks. + """ + + def __init__(self, chunk_size: int | None = None) -> None: + self._buffer = io.BytesIO() + self._chunk_size = chunk_size + + def decode(self, content: bytes) -> list[bytes]: + if self._chunk_size is None: + return [content] if content else [] + + self._buffer.write(content) + if self._buffer.tell() >= self._chunk_size: + value = self._buffer.getvalue() + chunks = [ + value[i : i + self._chunk_size] + for i in range(0, len(value), self._chunk_size) + ] + if len(chunks[-1]) == self._chunk_size: + self._buffer.seek(0) + self._buffer.truncate() + return chunks + else: + self._buffer.seek(0) + self._buffer.write(chunks[-1]) + self._buffer.truncate() + return chunks[:-1] + else: + return [] + + def flush(self) -> list[bytes]: + value = self._buffer.getvalue() + self._buffer.seek(0) + self._buffer.truncate() + return [value] if value else [] + + +class TextChunker: + """ + Handles returning text content in fixed-size chunks. + """ + + def __init__(self, chunk_size: int | None = None) -> None: + self._buffer = io.StringIO() + self._chunk_size = chunk_size + + def decode(self, content: str) -> list[str]: + if self._chunk_size is None: + return [content] if content else [] + + self._buffer.write(content) + if self._buffer.tell() >= self._chunk_size: + value = self._buffer.getvalue() + chunks = [ + value[i : i + self._chunk_size] + for i in range(0, len(value), self._chunk_size) + ] + if len(chunks[-1]) == self._chunk_size: + self._buffer.seek(0) + self._buffer.truncate() + return chunks + else: + self._buffer.seek(0) + self._buffer.write(chunks[-1]) + self._buffer.truncate() + return chunks[:-1] + else: + return [] + + def flush(self) -> list[str]: + value = self._buffer.getvalue() + self._buffer.seek(0) + self._buffer.truncate() + return [value] if value else [] + + +class TextDecoder: + """ + Handles incrementally decoding bytes into text + """ + + def __init__(self, encoding: str = "utf-8") -> None: + self.decoder = codecs.getincrementaldecoder(encoding)(errors="replace") + + def decode(self, data: bytes) -> str: + return self.decoder.decode(data) + + def flush(self) -> str: + return self.decoder.decode(b"", True) + + +class LineDecoder: + """ + Handles incrementally reading lines from text. + + Has the same behaviour as the stdllib splitlines, + but handling the input iteratively. + """ + + def __init__(self) -> None: + self.buffer: list[str] = [] + self.trailing_cr: bool = False + + def decode(self, text: str) -> list[str]: + # See https://docs.python.org/3/library/stdtypes.html#str.splitlines + NEWLINE_CHARS = "\n\r\x0b\x0c\x1c\x1d\x1e\x85\u2028\u2029" + + # We always push a trailing `\r` into the next decode iteration. + if self.trailing_cr: + text = "\r" + text + self.trailing_cr = False + if text.endswith("\r"): + self.trailing_cr = True + text = text[:-1] + + if not text: + # NOTE: the edge case input of empty text doesn't occur in practice, + # because other httpx internals filter out this value + return [] # pragma: no cover + + trailing_newline = text[-1] in NEWLINE_CHARS + lines = text.splitlines() + + if len(lines) == 1 and not trailing_newline: + # No new lines, buffer the input and continue. + self.buffer.append(lines[0]) + return [] + + if self.buffer: + # Include any existing buffer in the first portion of the + # splitlines result. + lines = ["".join(self.buffer) + lines[0]] + lines[1:] + self.buffer = [] + + if not trailing_newline: + # If the last segment of splitlines is not newline terminated, + # then drop it from our output and start a new buffer. + self.buffer = [lines.pop()] + + return lines + + def flush(self) -> list[str]: + if not self.buffer and not self.trailing_cr: + return [] + + lines = ["".join(self.buffer)] + self.buffer = [] + self.trailing_cr = False + return lines + + +SUPPORTED_DECODERS = { + "identity": IdentityDecoder, + "gzip": GZipDecoder, + "deflate": DeflateDecoder, + "br": BrotliDecoder, + "zstd": ZStandardDecoder, +} + + +if brotli is None: + SUPPORTED_DECODERS.pop("br") # pragma: no cover +if zstandard is None: + SUPPORTED_DECODERS.pop("zstd") # pragma: no cover diff --git a/.venv/Lib/site-packages/httpx/_exceptions.py b/.venv/Lib/site-packages/httpx/_exceptions.py new file mode 100644 index 0000000..77f45a6 --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_exceptions.py @@ -0,0 +1,379 @@ +""" +Our exception hierarchy: + +* HTTPError + x RequestError + + TransportError + - TimeoutException + · ConnectTimeout + · ReadTimeout + · WriteTimeout + · PoolTimeout + - NetworkError + · ConnectError + · ReadError + · WriteError + · CloseError + - ProtocolError + · LocalProtocolError + · RemoteProtocolError + - ProxyError + - UnsupportedProtocol + + DecodingError + + TooManyRedirects + x HTTPStatusError +* InvalidURL +* CookieConflict +* StreamError + x StreamConsumed + x StreamClosed + x ResponseNotRead + x RequestNotRead +""" + +from __future__ import annotations + +import contextlib +import typing + +if typing.TYPE_CHECKING: + from ._models import Request, Response # pragma: no cover + +__all__ = [ + "CloseError", + "ConnectError", + "ConnectTimeout", + "CookieConflict", + "DecodingError", + "HTTPError", + "HTTPStatusError", + "InvalidURL", + "LocalProtocolError", + "NetworkError", + "PoolTimeout", + "ProtocolError", + "ProxyError", + "ReadError", + "ReadTimeout", + "RemoteProtocolError", + "RequestError", + "RequestNotRead", + "ResponseNotRead", + "StreamClosed", + "StreamConsumed", + "StreamError", + "TimeoutException", + "TooManyRedirects", + "TransportError", + "UnsupportedProtocol", + "WriteError", + "WriteTimeout", +] + + +class HTTPError(Exception): + """ + Base class for `RequestError` and `HTTPStatusError`. + + Useful for `try...except` blocks when issuing a request, + and then calling `.raise_for_status()`. + + For example: + + ``` + try: + response = httpx.get("https://www.example.com") + response.raise_for_status() + except httpx.HTTPError as exc: + print(f"HTTP Exception for {exc.request.url} - {exc}") + ``` + """ + + def __init__(self, message: str) -> None: + super().__init__(message) + self._request: Request | None = None + + @property + def request(self) -> Request: + if self._request is None: + raise RuntimeError("The .request property has not been set.") + return self._request + + @request.setter + def request(self, request: Request) -> None: + self._request = request + + +class RequestError(HTTPError): + """ + Base class for all exceptions that may occur when issuing a `.request()`. + """ + + def __init__(self, message: str, *, request: Request | None = None) -> None: + super().__init__(message) + # At the point an exception is raised we won't typically have a request + # instance to associate it with. + # + # The 'request_context' context manager is used within the Client and + # Response methods in order to ensure that any raised exceptions + # have a `.request` property set on them. + self._request = request + + +class TransportError(RequestError): + """ + Base class for all exceptions that occur at the level of the Transport API. + """ + + +# Timeout exceptions... + + +class TimeoutException(TransportError): + """ + The base class for timeout errors. + + An operation has timed out. + """ + + +class ConnectTimeout(TimeoutException): + """ + Timed out while connecting to the host. + """ + + +class ReadTimeout(TimeoutException): + """ + Timed out while receiving data from the host. + """ + + +class WriteTimeout(TimeoutException): + """ + Timed out while sending data to the host. + """ + + +class PoolTimeout(TimeoutException): + """ + Timed out waiting to acquire a connection from the pool. + """ + + +# Core networking exceptions... + + +class NetworkError(TransportError): + """ + The base class for network-related errors. + + An error occurred while interacting with the network. + """ + + +class ReadError(NetworkError): + """ + Failed to receive data from the network. + """ + + +class WriteError(NetworkError): + """ + Failed to send data through the network. + """ + + +class ConnectError(NetworkError): + """ + Failed to establish a connection. + """ + + +class CloseError(NetworkError): + """ + Failed to close a connection. + """ + + +# Other transport exceptions... + + +class ProxyError(TransportError): + """ + An error occurred while establishing a proxy connection. + """ + + +class UnsupportedProtocol(TransportError): + """ + Attempted to make a request to an unsupported protocol. + + For example issuing a request to `ftp://www.example.com`. + """ + + +class ProtocolError(TransportError): + """ + The protocol was violated. + """ + + +class LocalProtocolError(ProtocolError): + """ + A protocol was violated by the client. + + For example if the user instantiated a `Request` instance explicitly, + failed to include the mandatory `Host:` header, and then issued it directly + using `client.send()`. + """ + + +class RemoteProtocolError(ProtocolError): + """ + The protocol was violated by the server. + + For example, returning malformed HTTP. + """ + + +# Other request exceptions... + + +class DecodingError(RequestError): + """ + Decoding of the response failed, due to a malformed encoding. + """ + + +class TooManyRedirects(RequestError): + """ + Too many redirects. + """ + + +# Client errors + + +class HTTPStatusError(HTTPError): + """ + The response had an error HTTP status of 4xx or 5xx. + + May be raised when calling `response.raise_for_status()` + """ + + def __init__(self, message: str, *, request: Request, response: Response) -> None: + super().__init__(message) + self.request = request + self.response = response + + +class InvalidURL(Exception): + """ + URL is improperly formed or cannot be parsed. + """ + + def __init__(self, message: str) -> None: + super().__init__(message) + + +class CookieConflict(Exception): + """ + Attempted to lookup a cookie by name, but multiple cookies existed. + + Can occur when calling `response.cookies.get(...)`. + """ + + def __init__(self, message: str) -> None: + super().__init__(message) + + +# Stream exceptions... + +# These may occur as the result of a programming error, by accessing +# the request/response stream in an invalid manner. + + +class StreamError(RuntimeError): + """ + The base class for stream exceptions. + + The developer made an error in accessing the request stream in + an invalid way. + """ + + def __init__(self, message: str) -> None: + super().__init__(message) + + +class StreamConsumed(StreamError): + """ + Attempted to read or stream content, but the content has already + been streamed. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream some content, but the content has " + "already been streamed. For requests, this could be due to passing " + "a generator as request content, and then receiving a redirect " + "response or a secondary request as part of an authentication flow." + "For responses, this could be due to attempting to stream the response " + "content more than once." + ) + super().__init__(message) + + +class StreamClosed(StreamError): + """ + Attempted to read or stream response content, but the request has been + closed. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream content, but the stream has " "been closed." + ) + super().__init__(message) + + +class ResponseNotRead(StreamError): + """ + Attempted to access streaming response content, without having called `read()`. + """ + + def __init__(self) -> None: + message = ( + "Attempted to access streaming response content," + " without having called `read()`." + ) + super().__init__(message) + + +class RequestNotRead(StreamError): + """ + Attempted to access streaming request content, without having called `read()`. + """ + + def __init__(self) -> None: + message = ( + "Attempted to access streaming request content," + " without having called `read()`." + ) + super().__init__(message) + + +@contextlib.contextmanager +def request_context( + request: Request | None = None, +) -> typing.Iterator[None]: + """ + A context manager that can be used to attach the given request context + to any `RequestError` exceptions that are raised within the block. + """ + try: + yield + except RequestError as exc: + if request is not None: + exc.request = request + raise exc diff --git a/.venv/Lib/site-packages/httpx/_main.py b/.venv/Lib/site-packages/httpx/_main.py new file mode 100644 index 0000000..cffa4bb --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_main.py @@ -0,0 +1,506 @@ +from __future__ import annotations + +import functools +import json +import sys +import typing + +import click +import pygments.lexers +import pygments.util +import rich.console +import rich.markup +import rich.progress +import rich.syntax +import rich.table + +from ._client import Client +from ._exceptions import RequestError +from ._models import Response +from ._status_codes import codes + +if typing.TYPE_CHECKING: + import httpcore # pragma: no cover + + +def print_help() -> None: + console = rich.console.Console() + + console.print("[bold]HTTPX :butterfly:", justify="center") + console.print() + console.print("A next generation HTTP client.", justify="center") + console.print() + console.print( + "Usage: [bold]httpx[/bold] [cyan] [OPTIONS][/cyan] ", justify="left" + ) + console.print() + + table = rich.table.Table.grid(padding=1, pad_edge=True) + table.add_column("Parameter", no_wrap=True, justify="left", style="bold") + table.add_column("Description") + table.add_row( + "-m, --method [cyan]METHOD", + "Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.\n" + "[Default: GET, or POST if a request body is included]", + ) + table.add_row( + "-p, --params [cyan] ...", + "Query parameters to include in the request URL.", + ) + table.add_row( + "-c, --content [cyan]TEXT", "Byte content to include in the request body." + ) + table.add_row( + "-d, --data [cyan] ...", "Form data to include in the request body." + ) + table.add_row( + "-f, --files [cyan] ...", + "Form files to include in the request body.", + ) + table.add_row("-j, --json [cyan]TEXT", "JSON data to include in the request body.") + table.add_row( + "-h, --headers [cyan] ...", + "Include additional HTTP headers in the request.", + ) + table.add_row( + "--cookies [cyan] ...", "Cookies to include in the request." + ) + table.add_row( + "--auth [cyan]", + "Username and password to include in the request. Specify '-' for the password" + " to use a password prompt. Note that using --verbose/-v will expose" + " the Authorization header, including the password encoding" + " in a trivially reversible format.", + ) + + table.add_row( + "--proxy [cyan]URL", + "Send the request via a proxy. Should be the URL giving the proxy address.", + ) + + table.add_row( + "--timeout [cyan]FLOAT", + "Timeout value to use for network operations, such as establishing the" + " connection, reading some data, etc... [Default: 5.0]", + ) + + table.add_row("--follow-redirects", "Automatically follow redirects.") + table.add_row("--no-verify", "Disable SSL verification.") + table.add_row( + "--http2", "Send the request using HTTP/2, if the remote server supports it." + ) + + table.add_row( + "--download [cyan]FILE", + "Save the response content as a file, rather than displaying it.", + ) + + table.add_row("-v, --verbose", "Verbose output. Show request as well as response.") + table.add_row("--help", "Show this message and exit.") + console.print(table) + + +def get_lexer_for_response(response: Response) -> str: + content_type = response.headers.get("Content-Type") + if content_type is not None: + mime_type, _, _ = content_type.partition(";") + try: + return typing.cast( + str, pygments.lexers.get_lexer_for_mimetype(mime_type.strip()).name + ) + except pygments.util.ClassNotFound: # pragma: no cover + pass + return "" # pragma: no cover + + +def format_request_headers(request: httpcore.Request, http2: bool = False) -> str: + version = "HTTP/2" if http2 else "HTTP/1.1" + headers = [ + (name.lower() if http2 else name, value) for name, value in request.headers + ] + method = request.method.decode("ascii") + target = request.url.target.decode("ascii") + lines = [f"{method} {target} {version}"] + [ + f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers + ] + return "\n".join(lines) + + +def format_response_headers( + http_version: bytes, + status: int, + reason_phrase: bytes | None, + headers: list[tuple[bytes, bytes]], +) -> str: + version = http_version.decode("ascii") + reason = ( + codes.get_reason_phrase(status) + if reason_phrase is None + else reason_phrase.decode("ascii") + ) + lines = [f"{version} {status} {reason}"] + [ + f"{name.decode('ascii')}: {value.decode('ascii')}" for name, value in headers + ] + return "\n".join(lines) + + +def print_request_headers(request: httpcore.Request, http2: bool = False) -> None: + console = rich.console.Console() + http_text = format_request_headers(request, http2=http2) + syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True) + console.print(syntax) + syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True) + console.print(syntax) + + +def print_response_headers( + http_version: bytes, + status: int, + reason_phrase: bytes | None, + headers: list[tuple[bytes, bytes]], +) -> None: + console = rich.console.Console() + http_text = format_response_headers(http_version, status, reason_phrase, headers) + syntax = rich.syntax.Syntax(http_text, "http", theme="ansi_dark", word_wrap=True) + console.print(syntax) + syntax = rich.syntax.Syntax("", "http", theme="ansi_dark", word_wrap=True) + console.print(syntax) + + +def print_response(response: Response) -> None: + console = rich.console.Console() + lexer_name = get_lexer_for_response(response) + if lexer_name: + if lexer_name.lower() == "json": + try: + data = response.json() + text = json.dumps(data, indent=4) + except ValueError: # pragma: no cover + text = response.text + else: + text = response.text + + syntax = rich.syntax.Syntax(text, lexer_name, theme="ansi_dark", word_wrap=True) + console.print(syntax) + else: + console.print(f"<{len(response.content)} bytes of binary data>") + + +_PCTRTT = typing.Tuple[typing.Tuple[str, str], ...] +_PCTRTTT = typing.Tuple[_PCTRTT, ...] +_PeerCertRetDictType = typing.Dict[str, typing.Union[str, _PCTRTTT, _PCTRTT]] + + +def format_certificate(cert: _PeerCertRetDictType) -> str: # pragma: no cover + lines = [] + for key, value in cert.items(): + if isinstance(value, (list, tuple)): + lines.append(f"* {key}:") + for item in value: + if key in ("subject", "issuer"): + for sub_item in item: + lines.append(f"* {sub_item[0]}: {sub_item[1]!r}") + elif isinstance(item, tuple) and len(item) == 2: + lines.append(f"* {item[0]}: {item[1]!r}") + else: + lines.append(f"* {item!r}") + else: + lines.append(f"* {key}: {value!r}") + return "\n".join(lines) + + +def trace( + name: str, info: typing.Mapping[str, typing.Any], verbose: bool = False +) -> None: + console = rich.console.Console() + if name == "connection.connect_tcp.started" and verbose: + host = info["host"] + console.print(f"* Connecting to {host!r}") + elif name == "connection.connect_tcp.complete" and verbose: + stream = info["return_value"] + server_addr = stream.get_extra_info("server_addr") + console.print(f"* Connected to {server_addr[0]!r} on port {server_addr[1]}") + elif name == "connection.start_tls.complete" and verbose: # pragma: no cover + stream = info["return_value"] + ssl_object = stream.get_extra_info("ssl_object") + version = ssl_object.version() + cipher = ssl_object.cipher() + server_cert = ssl_object.getpeercert() + alpn = ssl_object.selected_alpn_protocol() + console.print(f"* SSL established using {version!r} / {cipher[0]!r}") + console.print(f"* Selected ALPN protocol: {alpn!r}") + if server_cert: + console.print("* Server certificate:") + console.print(format_certificate(server_cert)) + elif name == "http11.send_request_headers.started" and verbose: + request = info["request"] + print_request_headers(request, http2=False) + elif name == "http2.send_request_headers.started" and verbose: # pragma: no cover + request = info["request"] + print_request_headers(request, http2=True) + elif name == "http11.receive_response_headers.complete": + http_version, status, reason_phrase, headers = info["return_value"] + print_response_headers(http_version, status, reason_phrase, headers) + elif name == "http2.receive_response_headers.complete": # pragma: no cover + status, headers = info["return_value"] + http_version = b"HTTP/2" + reason_phrase = None + print_response_headers(http_version, status, reason_phrase, headers) + + +def download_response(response: Response, download: typing.BinaryIO) -> None: + console = rich.console.Console() + console.print() + content_length = response.headers.get("Content-Length") + with rich.progress.Progress( + "[progress.description]{task.description}", + "[progress.percentage]{task.percentage:>3.0f}%", + rich.progress.BarColumn(bar_width=None), + rich.progress.DownloadColumn(), + rich.progress.TransferSpeedColumn(), + ) as progress: + description = f"Downloading [bold]{rich.markup.escape(download.name)}" + download_task = progress.add_task( + description, + total=int(content_length or 0), + start=content_length is not None, + ) + for chunk in response.iter_bytes(): + download.write(chunk) + progress.update(download_task, completed=response.num_bytes_downloaded) + + +def validate_json( + ctx: click.Context, + param: click.Option | click.Parameter, + value: typing.Any, +) -> typing.Any: + if value is None: + return None + + try: + return json.loads(value) + except json.JSONDecodeError: # pragma: no cover + raise click.BadParameter("Not valid JSON") + + +def validate_auth( + ctx: click.Context, + param: click.Option | click.Parameter, + value: typing.Any, +) -> typing.Any: + if value == (None, None): + return None + + username, password = value + if password == "-": # pragma: no cover + password = click.prompt("Password", hide_input=True) + return (username, password) + + +def handle_help( + ctx: click.Context, + param: click.Option | click.Parameter, + value: typing.Any, +) -> None: + if not value or ctx.resilient_parsing: + return + + print_help() + ctx.exit() + + +@click.command(add_help_option=False) +@click.argument("url", type=str) +@click.option( + "--method", + "-m", + "method", + type=str, + help=( + "Request method, such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD. " + "[Default: GET, or POST if a request body is included]" + ), +) +@click.option( + "--params", + "-p", + "params", + type=(str, str), + multiple=True, + help="Query parameters to include in the request URL.", +) +@click.option( + "--content", + "-c", + "content", + type=str, + help="Byte content to include in the request body.", +) +@click.option( + "--data", + "-d", + "data", + type=(str, str), + multiple=True, + help="Form data to include in the request body.", +) +@click.option( + "--files", + "-f", + "files", + type=(str, click.File(mode="rb")), + multiple=True, + help="Form files to include in the request body.", +) +@click.option( + "--json", + "-j", + "json", + type=str, + callback=validate_json, + help="JSON data to include in the request body.", +) +@click.option( + "--headers", + "-h", + "headers", + type=(str, str), + multiple=True, + help="Include additional HTTP headers in the request.", +) +@click.option( + "--cookies", + "cookies", + type=(str, str), + multiple=True, + help="Cookies to include in the request.", +) +@click.option( + "--auth", + "auth", + type=(str, str), + default=(None, None), + callback=validate_auth, + help=( + "Username and password to include in the request. " + "Specify '-' for the password to use a password prompt. " + "Note that using --verbose/-v will expose the Authorization header, " + "including the password encoding in a trivially reversible format." + ), +) +@click.option( + "--proxy", + "proxy", + type=str, + default=None, + help="Send the request via a proxy. Should be the URL giving the proxy address.", +) +@click.option( + "--timeout", + "timeout", + type=float, + default=5.0, + help=( + "Timeout value to use for network operations, such as establishing the " + "connection, reading some data, etc... [Default: 5.0]" + ), +) +@click.option( + "--follow-redirects", + "follow_redirects", + is_flag=True, + default=False, + help="Automatically follow redirects.", +) +@click.option( + "--no-verify", + "verify", + is_flag=True, + default=True, + help="Disable SSL verification.", +) +@click.option( + "--http2", + "http2", + type=bool, + is_flag=True, + default=False, + help="Send the request using HTTP/2, if the remote server supports it.", +) +@click.option( + "--download", + type=click.File("wb"), + help="Save the response content as a file, rather than displaying it.", +) +@click.option( + "--verbose", + "-v", + type=bool, + is_flag=True, + default=False, + help="Verbose. Show request as well as response.", +) +@click.option( + "--help", + is_flag=True, + is_eager=True, + expose_value=False, + callback=handle_help, + help="Show this message and exit.", +) +def main( + url: str, + method: str, + params: list[tuple[str, str]], + content: str, + data: list[tuple[str, str]], + files: list[tuple[str, click.File]], + json: str, + headers: list[tuple[str, str]], + cookies: list[tuple[str, str]], + auth: tuple[str, str] | None, + proxy: str, + timeout: float, + follow_redirects: bool, + verify: bool, + http2: bool, + download: typing.BinaryIO | None, + verbose: bool, +) -> None: + """ + An HTTP command line client. + Sends a request and displays the response. + """ + if not method: + method = "POST" if content or data or files or json else "GET" + + try: + with Client(proxy=proxy, timeout=timeout, http2=http2, verify=verify) as client: + with client.stream( + method, + url, + params=list(params), + content=content, + data=dict(data), + files=files, # type: ignore + json=json, + headers=headers, + cookies=dict(cookies), + auth=auth, + follow_redirects=follow_redirects, + extensions={"trace": functools.partial(trace, verbose=verbose)}, + ) as response: + if download is not None: + download_response(response, download) + else: + response.read() + if response.content: + print_response(response) + + except RequestError as exc: + console = rich.console.Console() + console.print(f"[red]{type(exc).__name__}[/red]: {exc}") + sys.exit(1) + + sys.exit(0 if response.is_success else 1) diff --git a/.venv/Lib/site-packages/httpx/_models.py b/.venv/Lib/site-packages/httpx/_models.py new file mode 100644 index 0000000..67d74bf --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_models.py @@ -0,0 +1,1277 @@ +from __future__ import annotations + +import codecs +import datetime +import email.message +import json as jsonlib +import re +import typing +import urllib.request +from collections.abc import Mapping +from http.cookiejar import Cookie, CookieJar + +from ._content import ByteStream, UnattachedStream, encode_request, encode_response +from ._decoders import ( + SUPPORTED_DECODERS, + ByteChunker, + ContentDecoder, + IdentityDecoder, + LineDecoder, + MultiDecoder, + TextChunker, + TextDecoder, +) +from ._exceptions import ( + CookieConflict, + HTTPStatusError, + RequestNotRead, + ResponseNotRead, + StreamClosed, + StreamConsumed, + request_context, +) +from ._multipart import get_multipart_boundary_from_content_type +from ._status_codes import codes +from ._types import ( + AsyncByteStream, + CookieTypes, + HeaderTypes, + QueryParamTypes, + RequestContent, + RequestData, + RequestExtensions, + RequestFiles, + ResponseContent, + ResponseExtensions, + SyncByteStream, +) +from ._urls import URL +from ._utils import to_bytes_or_str, to_str + +__all__ = ["Cookies", "Headers", "Request", "Response"] + +SENSITIVE_HEADERS = {"authorization", "proxy-authorization"} + + +def _is_known_encoding(encoding: str) -> bool: + """ + Return `True` if `encoding` is a known codec. + """ + try: + codecs.lookup(encoding) + except LookupError: + return False + return True + + +def _normalize_header_key(key: str | bytes, encoding: str | None = None) -> bytes: + """ + Coerce str/bytes into a strictly byte-wise HTTP header key. + """ + return key if isinstance(key, bytes) else key.encode(encoding or "ascii") + + +def _normalize_header_value(value: str | bytes, encoding: str | None = None) -> bytes: + """ + Coerce str/bytes into a strictly byte-wise HTTP header value. + """ + if isinstance(value, bytes): + return value + if not isinstance(value, str): + raise TypeError(f"Header value must be str or bytes, not {type(value)}") + return value.encode(encoding or "ascii") + + +def _parse_content_type_charset(content_type: str) -> str | None: + # We used to use `cgi.parse_header()` here, but `cgi` became a dead battery. + # See: https://peps.python.org/pep-0594/#cgi + msg = email.message.Message() + msg["content-type"] = content_type + return msg.get_content_charset(failobj=None) + + +def _parse_header_links(value: str) -> list[dict[str, str]]: + """ + Returns a list of parsed link headers, for more info see: + https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link + The generic syntax of those is: + Link: < uri-reference >; param1=value1; param2="value2" + So for instance: + Link; '; type="image/jpeg",;' + would return + [ + {"url": "http:/.../front.jpeg", "type": "image/jpeg"}, + {"url": "http://.../back.jpeg"}, + ] + :param value: HTTP Link entity-header field + :return: list of parsed link headers + """ + links: list[dict[str, str]] = [] + replace_chars = " '\"" + value = value.strip(replace_chars) + if not value: + return links + for val in re.split(", *<", value): + try: + url, params = val.split(";", 1) + except ValueError: + url, params = val, "" + link = {"url": url.strip("<> '\"")} + for param in params.split(";"): + try: + key, value = param.split("=") + except ValueError: + break + link[key.strip(replace_chars)] = value.strip(replace_chars) + links.append(link) + return links + + +def _obfuscate_sensitive_headers( + items: typing.Iterable[tuple[typing.AnyStr, typing.AnyStr]], +) -> typing.Iterator[tuple[typing.AnyStr, typing.AnyStr]]: + for k, v in items: + if to_str(k.lower()) in SENSITIVE_HEADERS: + v = to_bytes_or_str("[secure]", match_type_of=v) + yield k, v + + +class Headers(typing.MutableMapping[str, str]): + """ + HTTP headers, as a case-insensitive multi-dict. + """ + + def __init__( + self, + headers: HeaderTypes | None = None, + encoding: str | None = None, + ) -> None: + self._list = [] # type: typing.List[typing.Tuple[bytes, bytes, bytes]] + + if isinstance(headers, Headers): + self._list = list(headers._list) + elif isinstance(headers, Mapping): + for k, v in headers.items(): + bytes_key = _normalize_header_key(k, encoding) + bytes_value = _normalize_header_value(v, encoding) + self._list.append((bytes_key, bytes_key.lower(), bytes_value)) + elif headers is not None: + for k, v in headers: + bytes_key = _normalize_header_key(k, encoding) + bytes_value = _normalize_header_value(v, encoding) + self._list.append((bytes_key, bytes_key.lower(), bytes_value)) + + self._encoding = encoding + + @property + def encoding(self) -> str: + """ + Header encoding is mandated as ascii, but we allow fallbacks to utf-8 + or iso-8859-1. + """ + if self._encoding is None: + for encoding in ["ascii", "utf-8"]: + for key, value in self.raw: + try: + key.decode(encoding) + value.decode(encoding) + except UnicodeDecodeError: + break + else: + # The else block runs if 'break' did not occur, meaning + # all values fitted the encoding. + self._encoding = encoding + break + else: + # The ISO-8859-1 encoding covers all 256 code points in a byte, + # so will never raise decode errors. + self._encoding = "iso-8859-1" + return self._encoding + + @encoding.setter + def encoding(self, value: str) -> None: + self._encoding = value + + @property + def raw(self) -> list[tuple[bytes, bytes]]: + """ + Returns a list of the raw header items, as byte pairs. + """ + return [(raw_key, value) for raw_key, _, value in self._list] + + def keys(self) -> typing.KeysView[str]: + return {key.decode(self.encoding): None for _, key, value in self._list}.keys() + + def values(self) -> typing.ValuesView[str]: + values_dict: dict[str, str] = {} + for _, key, value in self._list: + str_key = key.decode(self.encoding) + str_value = value.decode(self.encoding) + if str_key in values_dict: + values_dict[str_key] += f", {str_value}" + else: + values_dict[str_key] = str_value + return values_dict.values() + + def items(self) -> typing.ItemsView[str, str]: + """ + Return `(key, value)` items of headers. Concatenate headers + into a single comma separated value when a key occurs multiple times. + """ + values_dict: dict[str, str] = {} + for _, key, value in self._list: + str_key = key.decode(self.encoding) + str_value = value.decode(self.encoding) + if str_key in values_dict: + values_dict[str_key] += f", {str_value}" + else: + values_dict[str_key] = str_value + return values_dict.items() + + def multi_items(self) -> list[tuple[str, str]]: + """ + Return a list of `(key, value)` pairs of headers. Allow multiple + occurrences of the same key without concatenating into a single + comma separated value. + """ + return [ + (key.decode(self.encoding), value.decode(self.encoding)) + for _, key, value in self._list + ] + + def get(self, key: str, default: typing.Any = None) -> typing.Any: + """ + Return a header value. If multiple occurrences of the header occur + then concatenate them together with commas. + """ + try: + return self[key] + except KeyError: + return default + + def get_list(self, key: str, split_commas: bool = False) -> list[str]: + """ + Return a list of all header values for a given key. + If `split_commas=True` is passed, then any comma separated header + values are split into multiple return strings. + """ + get_header_key = key.lower().encode(self.encoding) + + values = [ + item_value.decode(self.encoding) + for _, item_key, item_value in self._list + if item_key.lower() == get_header_key + ] + + if not split_commas: + return values + + split_values = [] + for value in values: + split_values.extend([item.strip() for item in value.split(",")]) + return split_values + + def update(self, headers: HeaderTypes | None = None) -> None: # type: ignore + headers = Headers(headers) + for key in headers.keys(): + if key in self: + self.pop(key) + self._list.extend(headers._list) + + def copy(self) -> Headers: + return Headers(self, encoding=self.encoding) + + def __getitem__(self, key: str) -> str: + """ + Return a single header value. + + If there are multiple headers with the same key, then we concatenate + them with commas. See: https://tools.ietf.org/html/rfc7230#section-3.2.2 + """ + normalized_key = key.lower().encode(self.encoding) + + items = [ + header_value.decode(self.encoding) + for _, header_key, header_value in self._list + if header_key == normalized_key + ] + + if items: + return ", ".join(items) + + raise KeyError(key) + + def __setitem__(self, key: str, value: str) -> None: + """ + Set the header `key` to `value`, removing any duplicate entries. + Retains insertion order. + """ + set_key = key.encode(self._encoding or "utf-8") + set_value = value.encode(self._encoding or "utf-8") + lookup_key = set_key.lower() + + found_indexes = [ + idx + for idx, (_, item_key, _) in enumerate(self._list) + if item_key == lookup_key + ] + + for idx in reversed(found_indexes[1:]): + del self._list[idx] + + if found_indexes: + idx = found_indexes[0] + self._list[idx] = (set_key, lookup_key, set_value) + else: + self._list.append((set_key, lookup_key, set_value)) + + def __delitem__(self, key: str) -> None: + """ + Remove the header `key`. + """ + del_key = key.lower().encode(self.encoding) + + pop_indexes = [ + idx + for idx, (_, item_key, _) in enumerate(self._list) + if item_key.lower() == del_key + ] + + if not pop_indexes: + raise KeyError(key) + + for idx in reversed(pop_indexes): + del self._list[idx] + + def __contains__(self, key: typing.Any) -> bool: + header_key = key.lower().encode(self.encoding) + return header_key in [key for _, key, _ in self._list] + + def __iter__(self) -> typing.Iterator[typing.Any]: + return iter(self.keys()) + + def __len__(self) -> int: + return len(self._list) + + def __eq__(self, other: typing.Any) -> bool: + try: + other_headers = Headers(other) + except ValueError: + return False + + self_list = [(key, value) for _, key, value in self._list] + other_list = [(key, value) for _, key, value in other_headers._list] + return sorted(self_list) == sorted(other_list) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + + encoding_str = "" + if self.encoding != "ascii": + encoding_str = f", encoding={self.encoding!r}" + + as_list = list(_obfuscate_sensitive_headers(self.multi_items())) + as_dict = dict(as_list) + + no_duplicate_keys = len(as_dict) == len(as_list) + if no_duplicate_keys: + return f"{class_name}({as_dict!r}{encoding_str})" + return f"{class_name}({as_list!r}{encoding_str})" + + +class Request: + def __init__( + self, + method: str, + url: URL | str, + *, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + content: RequestContent | None = None, + data: RequestData | None = None, + files: RequestFiles | None = None, + json: typing.Any | None = None, + stream: SyncByteStream | AsyncByteStream | None = None, + extensions: RequestExtensions | None = None, + ) -> None: + self.method = method.upper() + self.url = URL(url) if params is None else URL(url, params=params) + self.headers = Headers(headers) + self.extensions = {} if extensions is None else dict(extensions) + + if cookies: + Cookies(cookies).set_cookie_header(self) + + if stream is None: + content_type: str | None = self.headers.get("content-type") + headers, stream = encode_request( + content=content, + data=data, + files=files, + json=json, + boundary=get_multipart_boundary_from_content_type( + content_type=content_type.encode(self.headers.encoding) + if content_type + else None + ), + ) + self._prepare(headers) + self.stream = stream + # Load the request body, except for streaming content. + if isinstance(stream, ByteStream): + self.read() + else: + # There's an important distinction between `Request(content=...)`, + # and `Request(stream=...)`. + # + # Using `content=...` implies automatically populated `Host` and content + # headers, of either `Content-Length: ...` or `Transfer-Encoding: chunked`. + # + # Using `stream=...` will not automatically include *any* + # auto-populated headers. + # + # As an end-user you don't really need `stream=...`. It's only + # useful when: + # + # * Preserving the request stream when copying requests, eg for redirects. + # * Creating request instances on the *server-side* of the transport API. + self.stream = stream + + def _prepare(self, default_headers: dict[str, str]) -> None: + for key, value in default_headers.items(): + # Ignore Transfer-Encoding if the Content-Length has been set explicitly. + if key.lower() == "transfer-encoding" and "Content-Length" in self.headers: + continue + self.headers.setdefault(key, value) + + auto_headers: list[tuple[bytes, bytes]] = [] + + has_host = "Host" in self.headers + has_content_length = ( + "Content-Length" in self.headers or "Transfer-Encoding" in self.headers + ) + + if not has_host and self.url.host: + auto_headers.append((b"Host", self.url.netloc)) + if not has_content_length and self.method in ("POST", "PUT", "PATCH"): + auto_headers.append((b"Content-Length", b"0")) + + self.headers = Headers(auto_headers + self.headers.raw) + + @property + def content(self) -> bytes: + if not hasattr(self, "_content"): + raise RequestNotRead() + return self._content + + def read(self) -> bytes: + """ + Read and return the request content. + """ + if not hasattr(self, "_content"): + assert isinstance(self.stream, typing.Iterable) + self._content = b"".join(self.stream) + if not isinstance(self.stream, ByteStream): + # If a streaming request has been read entirely into memory, then + # we can replace the stream with a raw bytes implementation, + # to ensure that any non-replayable streams can still be used. + self.stream = ByteStream(self._content) + return self._content + + async def aread(self) -> bytes: + """ + Read and return the request content. + """ + if not hasattr(self, "_content"): + assert isinstance(self.stream, typing.AsyncIterable) + self._content = b"".join([part async for part in self.stream]) + if not isinstance(self.stream, ByteStream): + # If a streaming request has been read entirely into memory, then + # we can replace the stream with a raw bytes implementation, + # to ensure that any non-replayable streams can still be used. + self.stream = ByteStream(self._content) + return self._content + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + url = str(self.url) + return f"<{class_name}({self.method!r}, {url!r})>" + + def __getstate__(self) -> dict[str, typing.Any]: + return { + name: value + for name, value in self.__dict__.items() + if name not in ["extensions", "stream"] + } + + def __setstate__(self, state: dict[str, typing.Any]) -> None: + for name, value in state.items(): + setattr(self, name, value) + self.extensions = {} + self.stream = UnattachedStream() + + +class Response: + def __init__( + self, + status_code: int, + *, + headers: HeaderTypes | None = None, + content: ResponseContent | None = None, + text: str | None = None, + html: str | None = None, + json: typing.Any = None, + stream: SyncByteStream | AsyncByteStream | None = None, + request: Request | None = None, + extensions: ResponseExtensions | None = None, + history: list[Response] | None = None, + default_encoding: str | typing.Callable[[bytes], str] = "utf-8", + ) -> None: + self.status_code = status_code + self.headers = Headers(headers) + + self._request: Request | None = request + + # When follow_redirects=False and a redirect is received, + # the client will set `response.next_request`. + self.next_request: Request | None = None + + self.extensions = {} if extensions is None else dict(extensions) + self.history = [] if history is None else list(history) + + self.is_closed = False + self.is_stream_consumed = False + + self.default_encoding = default_encoding + + if stream is None: + headers, stream = encode_response(content, text, html, json) + self._prepare(headers) + self.stream = stream + if isinstance(stream, ByteStream): + # Load the response body, except for streaming content. + self.read() + else: + # There's an important distinction between `Response(content=...)`, + # and `Response(stream=...)`. + # + # Using `content=...` implies automatically populated content headers, + # of either `Content-Length: ...` or `Transfer-Encoding: chunked`. + # + # Using `stream=...` will not automatically include any content headers. + # + # As an end-user you don't really need `stream=...`. It's only + # useful when creating response instances having received a stream + # from the transport API. + self.stream = stream + + self._num_bytes_downloaded = 0 + + def _prepare(self, default_headers: dict[str, str]) -> None: + for key, value in default_headers.items(): + # Ignore Transfer-Encoding if the Content-Length has been set explicitly. + if key.lower() == "transfer-encoding" and "content-length" in self.headers: + continue + self.headers.setdefault(key, value) + + @property + def elapsed(self) -> datetime.timedelta: + """ + Returns the time taken for the complete request/response + cycle to complete. + """ + if not hasattr(self, "_elapsed"): + raise RuntimeError( + "'.elapsed' may only be accessed after the response " + "has been read or closed." + ) + return self._elapsed + + @elapsed.setter + def elapsed(self, elapsed: datetime.timedelta) -> None: + self._elapsed = elapsed + + @property + def request(self) -> Request: + """ + Returns the request instance associated to the current response. + """ + if self._request is None: + raise RuntimeError( + "The request instance has not been set on this response." + ) + return self._request + + @request.setter + def request(self, value: Request) -> None: + self._request = value + + @property + def http_version(self) -> str: + try: + http_version: bytes = self.extensions["http_version"] + except KeyError: + return "HTTP/1.1" + else: + return http_version.decode("ascii", errors="ignore") + + @property + def reason_phrase(self) -> str: + try: + reason_phrase: bytes = self.extensions["reason_phrase"] + except KeyError: + return codes.get_reason_phrase(self.status_code) + else: + return reason_phrase.decode("ascii", errors="ignore") + + @property + def url(self) -> URL: + """ + Returns the URL for which the request was made. + """ + return self.request.url + + @property + def content(self) -> bytes: + if not hasattr(self, "_content"): + raise ResponseNotRead() + return self._content + + @property + def text(self) -> str: + if not hasattr(self, "_text"): + content = self.content + if not content: + self._text = "" + else: + decoder = TextDecoder(encoding=self.encoding or "utf-8") + self._text = "".join([decoder.decode(self.content), decoder.flush()]) + return self._text + + @property + def encoding(self) -> str | None: + """ + Return an encoding to use for decoding the byte content into text. + The priority for determining this is given by... + + * `.encoding = <>` has been set explicitly. + * The encoding as specified by the charset parameter in the Content-Type header. + * The encoding as determined by `default_encoding`, which may either be + a string like "utf-8" indicating the encoding to use, or may be a callable + which enables charset autodetection. + """ + if not hasattr(self, "_encoding"): + encoding = self.charset_encoding + if encoding is None or not _is_known_encoding(encoding): + if isinstance(self.default_encoding, str): + encoding = self.default_encoding + elif hasattr(self, "_content"): + encoding = self.default_encoding(self._content) + self._encoding = encoding or "utf-8" + return self._encoding + + @encoding.setter + def encoding(self, value: str) -> None: + """ + Set the encoding to use for decoding the byte content into text. + + If the `text` attribute has been accessed, attempting to set the + encoding will throw a ValueError. + """ + if hasattr(self, "_text"): + raise ValueError( + "Setting encoding after `text` has been accessed is not allowed." + ) + self._encoding = value + + @property + def charset_encoding(self) -> str | None: + """ + Return the encoding, as specified by the Content-Type header. + """ + content_type = self.headers.get("Content-Type") + if content_type is None: + return None + + return _parse_content_type_charset(content_type) + + def _get_content_decoder(self) -> ContentDecoder: + """ + Returns a decoder instance which can be used to decode the raw byte + content, depending on the Content-Encoding used in the response. + """ + if not hasattr(self, "_decoder"): + decoders: list[ContentDecoder] = [] + values = self.headers.get_list("content-encoding", split_commas=True) + for value in values: + value = value.strip().lower() + try: + decoder_cls = SUPPORTED_DECODERS[value] + decoders.append(decoder_cls()) + except KeyError: + continue + + if len(decoders) == 1: + self._decoder = decoders[0] + elif len(decoders) > 1: + self._decoder = MultiDecoder(children=decoders) + else: + self._decoder = IdentityDecoder() + + return self._decoder + + @property + def is_informational(self) -> bool: + """ + A property which is `True` for 1xx status codes, `False` otherwise. + """ + return codes.is_informational(self.status_code) + + @property + def is_success(self) -> bool: + """ + A property which is `True` for 2xx status codes, `False` otherwise. + """ + return codes.is_success(self.status_code) + + @property + def is_redirect(self) -> bool: + """ + A property which is `True` for 3xx status codes, `False` otherwise. + + Note that not all responses with a 3xx status code indicate a URL redirect. + + Use `response.has_redirect_location` to determine responses with a properly + formed URL redirection. + """ + return codes.is_redirect(self.status_code) + + @property + def is_client_error(self) -> bool: + """ + A property which is `True` for 4xx status codes, `False` otherwise. + """ + return codes.is_client_error(self.status_code) + + @property + def is_server_error(self) -> bool: + """ + A property which is `True` for 5xx status codes, `False` otherwise. + """ + return codes.is_server_error(self.status_code) + + @property + def is_error(self) -> bool: + """ + A property which is `True` for 4xx and 5xx status codes, `False` otherwise. + """ + return codes.is_error(self.status_code) + + @property + def has_redirect_location(self) -> bool: + """ + Returns True for 3xx responses with a properly formed URL redirection, + `False` otherwise. + """ + return ( + self.status_code + in ( + # 301 (Cacheable redirect. Method may change to GET.) + codes.MOVED_PERMANENTLY, + # 302 (Uncacheable redirect. Method may change to GET.) + codes.FOUND, + # 303 (Client should make a GET or HEAD request.) + codes.SEE_OTHER, + # 307 (Equiv. 302, but retain method) + codes.TEMPORARY_REDIRECT, + # 308 (Equiv. 301, but retain method) + codes.PERMANENT_REDIRECT, + ) + and "Location" in self.headers + ) + + def raise_for_status(self) -> Response: + """ + Raise the `HTTPStatusError` if one occurred. + """ + request = self._request + if request is None: + raise RuntimeError( + "Cannot call `raise_for_status` as the request " + "instance has not been set on this response." + ) + + if self.is_success: + return self + + if self.has_redirect_location: + message = ( + "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n" + "Redirect location: '{0.headers[location]}'\n" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}" + ) + else: + message = ( + "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n" + "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}" + ) + + status_class = self.status_code // 100 + error_types = { + 1: "Informational response", + 3: "Redirect response", + 4: "Client error", + 5: "Server error", + } + error_type = error_types.get(status_class, "Invalid status code") + message = message.format(self, error_type=error_type) + raise HTTPStatusError(message, request=request, response=self) + + def json(self, **kwargs: typing.Any) -> typing.Any: + return jsonlib.loads(self.content, **kwargs) + + @property + def cookies(self) -> Cookies: + if not hasattr(self, "_cookies"): + self._cookies = Cookies() + self._cookies.extract_cookies(self) + return self._cookies + + @property + def links(self) -> dict[str | None, dict[str, str]]: + """ + Returns the parsed header links of the response, if any + """ + header = self.headers.get("link") + if header is None: + return {} + + return { + (link.get("rel") or link.get("url")): link + for link in _parse_header_links(header) + } + + @property + def num_bytes_downloaded(self) -> int: + return self._num_bytes_downloaded + + def __repr__(self) -> str: + return f"" + + def __getstate__(self) -> dict[str, typing.Any]: + return { + name: value + for name, value in self.__dict__.items() + if name not in ["extensions", "stream", "is_closed", "_decoder"] + } + + def __setstate__(self, state: dict[str, typing.Any]) -> None: + for name, value in state.items(): + setattr(self, name, value) + self.is_closed = True + self.extensions = {} + self.stream = UnattachedStream() + + def read(self) -> bytes: + """ + Read and return the response content. + """ + if not hasattr(self, "_content"): + self._content = b"".join(self.iter_bytes()) + return self._content + + def iter_bytes(self, chunk_size: int | None = None) -> typing.Iterator[bytes]: + """ + A byte-iterator over the decoded response content. + This allows us to handle gzip, deflate, brotli, and zstd encoded responses. + """ + if hasattr(self, "_content"): + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), max(chunk_size, 1)): + yield self._content[i : i + chunk_size] + else: + decoder = self._get_content_decoder() + chunker = ByteChunker(chunk_size=chunk_size) + with request_context(request=self._request): + for raw_bytes in self.iter_raw(): + decoded = decoder.decode(raw_bytes) + for chunk in chunker.decode(decoded): + yield chunk + decoded = decoder.flush() + for chunk in chunker.decode(decoded): + yield chunk # pragma: no cover + for chunk in chunker.flush(): + yield chunk + + def iter_text(self, chunk_size: int | None = None) -> typing.Iterator[str]: + """ + A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + decoder = TextDecoder(encoding=self.encoding or "utf-8") + chunker = TextChunker(chunk_size=chunk_size) + with request_context(request=self._request): + for byte_content in self.iter_bytes(): + text_content = decoder.decode(byte_content) + for chunk in chunker.decode(text_content): + yield chunk + text_content = decoder.flush() + for chunk in chunker.decode(text_content): + yield chunk # pragma: no cover + for chunk in chunker.flush(): + yield chunk + + def iter_lines(self) -> typing.Iterator[str]: + decoder = LineDecoder() + with request_context(request=self._request): + for text in self.iter_text(): + for line in decoder.decode(text): + yield line + for line in decoder.flush(): + yield line + + def iter_raw(self, chunk_size: int | None = None) -> typing.Iterator[bytes]: + """ + A byte-iterator over the raw response content. + """ + if self.is_stream_consumed: + raise StreamConsumed() + if self.is_closed: + raise StreamClosed() + if not isinstance(self.stream, SyncByteStream): + raise RuntimeError("Attempted to call a sync iterator on an async stream.") + + self.is_stream_consumed = True + self._num_bytes_downloaded = 0 + chunker = ByteChunker(chunk_size=chunk_size) + + with request_context(request=self._request): + for raw_stream_bytes in self.stream: + self._num_bytes_downloaded += len(raw_stream_bytes) + for chunk in chunker.decode(raw_stream_bytes): + yield chunk + + for chunk in chunker.flush(): + yield chunk + + self.close() + + def close(self) -> None: + """ + Close the response and release the connection. + Automatically called if the response body is read to completion. + """ + if not isinstance(self.stream, SyncByteStream): + raise RuntimeError("Attempted to call an sync close on an async stream.") + + if not self.is_closed: + self.is_closed = True + with request_context(request=self._request): + self.stream.close() + + async def aread(self) -> bytes: + """ + Read and return the response content. + """ + if not hasattr(self, "_content"): + self._content = b"".join([part async for part in self.aiter_bytes()]) + return self._content + + async def aiter_bytes( + self, chunk_size: int | None = None + ) -> typing.AsyncIterator[bytes]: + """ + A byte-iterator over the decoded response content. + This allows us to handle gzip, deflate, brotli, and zstd encoded responses. + """ + if hasattr(self, "_content"): + chunk_size = len(self._content) if chunk_size is None else chunk_size + for i in range(0, len(self._content), max(chunk_size, 1)): + yield self._content[i : i + chunk_size] + else: + decoder = self._get_content_decoder() + chunker = ByteChunker(chunk_size=chunk_size) + with request_context(request=self._request): + async for raw_bytes in self.aiter_raw(): + decoded = decoder.decode(raw_bytes) + for chunk in chunker.decode(decoded): + yield chunk + decoded = decoder.flush() + for chunk in chunker.decode(decoded): + yield chunk # pragma: no cover + for chunk in chunker.flush(): + yield chunk + + async def aiter_text( + self, chunk_size: int | None = None + ) -> typing.AsyncIterator[str]: + """ + A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + decoder = TextDecoder(encoding=self.encoding or "utf-8") + chunker = TextChunker(chunk_size=chunk_size) + with request_context(request=self._request): + async for byte_content in self.aiter_bytes(): + text_content = decoder.decode(byte_content) + for chunk in chunker.decode(text_content): + yield chunk + text_content = decoder.flush() + for chunk in chunker.decode(text_content): + yield chunk # pragma: no cover + for chunk in chunker.flush(): + yield chunk + + async def aiter_lines(self) -> typing.AsyncIterator[str]: + decoder = LineDecoder() + with request_context(request=self._request): + async for text in self.aiter_text(): + for line in decoder.decode(text): + yield line + for line in decoder.flush(): + yield line + + async def aiter_raw( + self, chunk_size: int | None = None + ) -> typing.AsyncIterator[bytes]: + """ + A byte-iterator over the raw response content. + """ + if self.is_stream_consumed: + raise StreamConsumed() + if self.is_closed: + raise StreamClosed() + if not isinstance(self.stream, AsyncByteStream): + raise RuntimeError("Attempted to call an async iterator on an sync stream.") + + self.is_stream_consumed = True + self._num_bytes_downloaded = 0 + chunker = ByteChunker(chunk_size=chunk_size) + + with request_context(request=self._request): + async for raw_stream_bytes in self.stream: + self._num_bytes_downloaded += len(raw_stream_bytes) + for chunk in chunker.decode(raw_stream_bytes): + yield chunk + + for chunk in chunker.flush(): + yield chunk + + await self.aclose() + + async def aclose(self) -> None: + """ + Close the response and release the connection. + Automatically called if the response body is read to completion. + """ + if not isinstance(self.stream, AsyncByteStream): + raise RuntimeError("Attempted to call an async close on an sync stream.") + + if not self.is_closed: + self.is_closed = True + with request_context(request=self._request): + await self.stream.aclose() + + +class Cookies(typing.MutableMapping[str, str]): + """ + HTTP Cookies, as a mutable mapping. + """ + + def __init__(self, cookies: CookieTypes | None = None) -> None: + if cookies is None or isinstance(cookies, dict): + self.jar = CookieJar() + if isinstance(cookies, dict): + for key, value in cookies.items(): + self.set(key, value) + elif isinstance(cookies, list): + self.jar = CookieJar() + for key, value in cookies: + self.set(key, value) + elif isinstance(cookies, Cookies): + self.jar = CookieJar() + for cookie in cookies.jar: + self.jar.set_cookie(cookie) + else: + self.jar = cookies + + def extract_cookies(self, response: Response) -> None: + """ + Loads any cookies based on the response `Set-Cookie` headers. + """ + urllib_response = self._CookieCompatResponse(response) + urllib_request = self._CookieCompatRequest(response.request) + + self.jar.extract_cookies(urllib_response, urllib_request) # type: ignore + + def set_cookie_header(self, request: Request) -> None: + """ + Sets an appropriate 'Cookie:' HTTP header on the `Request`. + """ + urllib_request = self._CookieCompatRequest(request) + self.jar.add_cookie_header(urllib_request) + + def set(self, name: str, value: str, domain: str = "", path: str = "/") -> None: + """ + Set a cookie value by name. May optionally include domain and path. + """ + kwargs = { + "version": 0, + "name": name, + "value": value, + "port": None, + "port_specified": False, + "domain": domain, + "domain_specified": bool(domain), + "domain_initial_dot": domain.startswith("."), + "path": path, + "path_specified": bool(path), + "secure": False, + "expires": None, + "discard": True, + "comment": None, + "comment_url": None, + "rest": {"HttpOnly": None}, + "rfc2109": False, + } + cookie = Cookie(**kwargs) # type: ignore + self.jar.set_cookie(cookie) + + def get( # type: ignore + self, + name: str, + default: str | None = None, + domain: str | None = None, + path: str | None = None, + ) -> str | None: + """ + Get a cookie by name. May optionally include domain and path + in order to specify exactly which cookie to retrieve. + """ + value = None + for cookie in self.jar: + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if value is not None: + message = f"Multiple cookies exist with name={name}" + raise CookieConflict(message) + value = cookie.value + + if value is None: + return default + return value + + def delete( + self, + name: str, + domain: str | None = None, + path: str | None = None, + ) -> None: + """ + Delete a cookie by name. May optionally include domain and path + in order to specify exactly which cookie to delete. + """ + if domain is not None and path is not None: + return self.jar.clear(domain, path, name) + + remove = [ + cookie + for cookie in self.jar + if cookie.name == name + and (domain is None or cookie.domain == domain) + and (path is None or cookie.path == path) + ] + + for cookie in remove: + self.jar.clear(cookie.domain, cookie.path, cookie.name) + + def clear(self, domain: str | None = None, path: str | None = None) -> None: + """ + Delete all cookies. Optionally include a domain and path in + order to only delete a subset of all the cookies. + """ + args = [] + if domain is not None: + args.append(domain) + if path is not None: + assert domain is not None + args.append(path) + self.jar.clear(*args) + + def update(self, cookies: CookieTypes | None = None) -> None: # type: ignore + cookies = Cookies(cookies) + for cookie in cookies.jar: + self.jar.set_cookie(cookie) + + def __setitem__(self, name: str, value: str) -> None: + return self.set(name, value) + + def __getitem__(self, name: str) -> str: + value = self.get(name) + if value is None: + raise KeyError(name) + return value + + def __delitem__(self, name: str) -> None: + return self.delete(name) + + def __len__(self) -> int: + return len(self.jar) + + def __iter__(self) -> typing.Iterator[str]: + return (cookie.name for cookie in self.jar) + + def __bool__(self) -> bool: + for _ in self.jar: + return True + return False + + def __repr__(self) -> str: + cookies_repr = ", ".join( + [ + f"" + for cookie in self.jar + ] + ) + + return f"" + + class _CookieCompatRequest(urllib.request.Request): + """ + Wraps a `Request` instance up in a compatibility interface suitable + for use with `CookieJar` operations. + """ + + def __init__(self, request: Request) -> None: + super().__init__( + url=str(request.url), + headers=dict(request.headers), + method=request.method, + ) + self.request = request + + def add_unredirected_header(self, key: str, value: str) -> None: + super().add_unredirected_header(key, value) + self.request.headers[key] = value + + class _CookieCompatResponse: + """ + Wraps a `Request` instance up in a compatibility interface suitable + for use with `CookieJar` operations. + """ + + def __init__(self, response: Response) -> None: + self.response = response + + def info(self) -> email.message.Message: + info = email.message.Message() + for key, value in self.response.headers.multi_items(): + # Note that setting `info[key]` here is an "append" operation, + # not a "replace" operation. + # https://docs.python.org/3/library/email.compat32-message.html#email.message.Message.__setitem__ + info[key] = value + return info diff --git a/.venv/Lib/site-packages/httpx/_multipart.py b/.venv/Lib/site-packages/httpx/_multipart.py new file mode 100644 index 0000000..b4761af --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_multipart.py @@ -0,0 +1,300 @@ +from __future__ import annotations + +import io +import mimetypes +import os +import re +import typing +from pathlib import Path + +from ._types import ( + AsyncByteStream, + FileContent, + FileTypes, + RequestData, + RequestFiles, + SyncByteStream, +) +from ._utils import ( + peek_filelike_length, + primitive_value_to_str, + to_bytes, +) + +_HTML5_FORM_ENCODING_REPLACEMENTS = {'"': "%22", "\\": "\\\\"} +_HTML5_FORM_ENCODING_REPLACEMENTS.update( + {chr(c): "%{:02X}".format(c) for c in range(0x1F + 1) if c != 0x1B} +) +_HTML5_FORM_ENCODING_RE = re.compile( + r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()]) +) + + +def _format_form_param(name: str, value: str) -> bytes: + """ + Encode a name/value pair within a multipart form. + """ + + def replacer(match: typing.Match[str]) -> str: + return _HTML5_FORM_ENCODING_REPLACEMENTS[match.group(0)] + + value = _HTML5_FORM_ENCODING_RE.sub(replacer, value) + return f'{name}="{value}"'.encode() + + +def _guess_content_type(filename: str | None) -> str | None: + """ + Guesses the mimetype based on a filename. Defaults to `application/octet-stream`. + + Returns `None` if `filename` is `None` or empty. + """ + if filename: + return mimetypes.guess_type(filename)[0] or "application/octet-stream" + return None + + +def get_multipart_boundary_from_content_type( + content_type: bytes | None, +) -> bytes | None: + if not content_type or not content_type.startswith(b"multipart/form-data"): + return None + # parse boundary according to + # https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1 + if b";" in content_type: + for section in content_type.split(b";"): + if section.strip().lower().startswith(b"boundary="): + return section.strip()[len(b"boundary=") :].strip(b'"') + return None + + +class DataField: + """ + A single form field item, within a multipart form field. + """ + + def __init__(self, name: str, value: str | bytes | int | float | None) -> None: + if not isinstance(name, str): + raise TypeError( + f"Invalid type for name. Expected str, got {type(name)}: {name!r}" + ) + if value is not None and not isinstance(value, (str, bytes, int, float)): + raise TypeError( + "Invalid type for value. Expected primitive type," + f" got {type(value)}: {value!r}" + ) + self.name = name + self.value: str | bytes = ( + value if isinstance(value, bytes) else primitive_value_to_str(value) + ) + + def render_headers(self) -> bytes: + if not hasattr(self, "_headers"): + name = _format_form_param("name", self.name) + self._headers = b"".join( + [b"Content-Disposition: form-data; ", name, b"\r\n\r\n"] + ) + + return self._headers + + def render_data(self) -> bytes: + if not hasattr(self, "_data"): + self._data = to_bytes(self.value) + + return self._data + + def get_length(self) -> int: + headers = self.render_headers() + data = self.render_data() + return len(headers) + len(data) + + def render(self) -> typing.Iterator[bytes]: + yield self.render_headers() + yield self.render_data() + + +class FileField: + """ + A single file field item, within a multipart form field. + """ + + CHUNK_SIZE = 64 * 1024 + + def __init__(self, name: str, value: FileTypes) -> None: + self.name = name + + fileobj: FileContent + + headers: dict[str, str] = {} + content_type: str | None = None + + # This large tuple based API largely mirror's requests' API + # It would be good to think of better APIs for this that we could + # include in httpx 2.0 since variable length tuples(especially of 4 elements) + # are quite unwieldly + if isinstance(value, tuple): + if len(value) == 2: + # neither the 3rd parameter (content_type) nor the 4th (headers) + # was included + filename, fileobj = value + elif len(value) == 3: + filename, fileobj, content_type = value + else: + # all 4 parameters included + filename, fileobj, content_type, headers = value # type: ignore + else: + filename = Path(str(getattr(value, "name", "upload"))).name + fileobj = value + + if content_type is None: + content_type = _guess_content_type(filename) + + has_content_type_header = any("content-type" in key.lower() for key in headers) + if content_type is not None and not has_content_type_header: + # note that unlike requests, we ignore the content_type provided in the 3rd + # tuple element if it is also included in the headers requests does + # the opposite (it overwrites the headerwith the 3rd tuple element) + headers["Content-Type"] = content_type + + if isinstance(fileobj, io.StringIO): + raise TypeError( + "Multipart file uploads require 'io.BytesIO', not 'io.StringIO'." + ) + if isinstance(fileobj, io.TextIOBase): + raise TypeError( + "Multipart file uploads must be opened in binary mode, not text mode." + ) + + self.filename = filename + self.file = fileobj + self.headers = headers + + def get_length(self) -> int | None: + headers = self.render_headers() + + if isinstance(self.file, (str, bytes)): + return len(headers) + len(to_bytes(self.file)) + + file_length = peek_filelike_length(self.file) + + # If we can't determine the filesize without reading it into memory, + # then return `None` here, to indicate an unknown file length. + if file_length is None: + return None + + return len(headers) + file_length + + def render_headers(self) -> bytes: + if not hasattr(self, "_headers"): + parts = [ + b"Content-Disposition: form-data; ", + _format_form_param("name", self.name), + ] + if self.filename: + filename = _format_form_param("filename", self.filename) + parts.extend([b"; ", filename]) + for header_name, header_value in self.headers.items(): + key, val = f"\r\n{header_name}: ".encode(), header_value.encode() + parts.extend([key, val]) + parts.append(b"\r\n\r\n") + self._headers = b"".join(parts) + + return self._headers + + def render_data(self) -> typing.Iterator[bytes]: + if isinstance(self.file, (str, bytes)): + yield to_bytes(self.file) + return + + if hasattr(self.file, "seek"): + try: + self.file.seek(0) + except io.UnsupportedOperation: + pass + + chunk = self.file.read(self.CHUNK_SIZE) + while chunk: + yield to_bytes(chunk) + chunk = self.file.read(self.CHUNK_SIZE) + + def render(self) -> typing.Iterator[bytes]: + yield self.render_headers() + yield from self.render_data() + + +class MultipartStream(SyncByteStream, AsyncByteStream): + """ + Request content as streaming multipart encoded form data. + """ + + def __init__( + self, + data: RequestData, + files: RequestFiles, + boundary: bytes | None = None, + ) -> None: + if boundary is None: + boundary = os.urandom(16).hex().encode("ascii") + + self.boundary = boundary + self.content_type = "multipart/form-data; boundary=%s" % boundary.decode( + "ascii" + ) + self.fields = list(self._iter_fields(data, files)) + + def _iter_fields( + self, data: RequestData, files: RequestFiles + ) -> typing.Iterator[FileField | DataField]: + for name, value in data.items(): + if isinstance(value, (tuple, list)): + for item in value: + yield DataField(name=name, value=item) + else: + yield DataField(name=name, value=value) + + file_items = files.items() if isinstance(files, typing.Mapping) else files + for name, value in file_items: + yield FileField(name=name, value=value) + + def iter_chunks(self) -> typing.Iterator[bytes]: + for field in self.fields: + yield b"--%s\r\n" % self.boundary + yield from field.render() + yield b"\r\n" + yield b"--%s--\r\n" % self.boundary + + def get_content_length(self) -> int | None: + """ + Return the length of the multipart encoded content, or `None` if + any of the files have a length that cannot be determined upfront. + """ + boundary_length = len(self.boundary) + length = 0 + + for field in self.fields: + field_length = field.get_length() + if field_length is None: + return None + + length += 2 + boundary_length + 2 # b"--{boundary}\r\n" + length += field_length + length += 2 # b"\r\n" + + length += 2 + boundary_length + 4 # b"--{boundary}--\r\n" + return length + + # Content stream interface. + + def get_headers(self) -> dict[str, str]: + content_length = self.get_content_length() + content_type = self.content_type + if content_length is None: + return {"Transfer-Encoding": "chunked", "Content-Type": content_type} + return {"Content-Length": str(content_length), "Content-Type": content_type} + + def __iter__(self) -> typing.Iterator[bytes]: + for chunk in self.iter_chunks(): + yield chunk + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + for chunk in self.iter_chunks(): + yield chunk diff --git a/.venv/Lib/site-packages/httpx/_status_codes.py b/.venv/Lib/site-packages/httpx/_status_codes.py new file mode 100644 index 0000000..133a623 --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_status_codes.py @@ -0,0 +1,162 @@ +from __future__ import annotations + +from enum import IntEnum + +__all__ = ["codes"] + + +class codes(IntEnum): + """HTTP status codes and reason phrases + + Status codes from the following RFCs are all observed: + + * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616 + * RFC 6585: Additional HTTP Status Codes + * RFC 3229: Delta encoding in HTTP + * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518 + * RFC 5842: Binding Extensions to WebDAV + * RFC 7238: Permanent Redirect + * RFC 2295: Transparent Content Negotiation in HTTP + * RFC 2774: An HTTP Extension Framework + * RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2) + * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0) + * RFC 7725: An HTTP Status Code to Report Legal Obstacles + * RFC 8297: An HTTP Status Code for Indicating Hints + * RFC 8470: Using Early Data in HTTP + """ + + def __new__(cls, value: int, phrase: str = "") -> codes: + obj = int.__new__(cls, value) + obj._value_ = value + + obj.phrase = phrase # type: ignore[attr-defined] + return obj + + def __str__(self) -> str: + return str(self.value) + + @classmethod + def get_reason_phrase(cls, value: int) -> str: + try: + return codes(value).phrase # type: ignore + except ValueError: + return "" + + @classmethod + def is_informational(cls, value: int) -> bool: + """ + Returns `True` for 1xx status codes, `False` otherwise. + """ + return 100 <= value <= 199 + + @classmethod + def is_success(cls, value: int) -> bool: + """ + Returns `True` for 2xx status codes, `False` otherwise. + """ + return 200 <= value <= 299 + + @classmethod + def is_redirect(cls, value: int) -> bool: + """ + Returns `True` for 3xx status codes, `False` otherwise. + """ + return 300 <= value <= 399 + + @classmethod + def is_client_error(cls, value: int) -> bool: + """ + Returns `True` for 4xx status codes, `False` otherwise. + """ + return 400 <= value <= 499 + + @classmethod + def is_server_error(cls, value: int) -> bool: + """ + Returns `True` for 5xx status codes, `False` otherwise. + """ + return 500 <= value <= 599 + + @classmethod + def is_error(cls, value: int) -> bool: + """ + Returns `True` for 4xx or 5xx status codes, `False` otherwise. + """ + return 400 <= value <= 599 + + # informational + CONTINUE = 100, "Continue" + SWITCHING_PROTOCOLS = 101, "Switching Protocols" + PROCESSING = 102, "Processing" + EARLY_HINTS = 103, "Early Hints" + + # success + OK = 200, "OK" + CREATED = 201, "Created" + ACCEPTED = 202, "Accepted" + NON_AUTHORITATIVE_INFORMATION = 203, "Non-Authoritative Information" + NO_CONTENT = 204, "No Content" + RESET_CONTENT = 205, "Reset Content" + PARTIAL_CONTENT = 206, "Partial Content" + MULTI_STATUS = 207, "Multi-Status" + ALREADY_REPORTED = 208, "Already Reported" + IM_USED = 226, "IM Used" + + # redirection + MULTIPLE_CHOICES = 300, "Multiple Choices" + MOVED_PERMANENTLY = 301, "Moved Permanently" + FOUND = 302, "Found" + SEE_OTHER = 303, "See Other" + NOT_MODIFIED = 304, "Not Modified" + USE_PROXY = 305, "Use Proxy" + TEMPORARY_REDIRECT = 307, "Temporary Redirect" + PERMANENT_REDIRECT = 308, "Permanent Redirect" + + # client error + BAD_REQUEST = 400, "Bad Request" + UNAUTHORIZED = 401, "Unauthorized" + PAYMENT_REQUIRED = 402, "Payment Required" + FORBIDDEN = 403, "Forbidden" + NOT_FOUND = 404, "Not Found" + METHOD_NOT_ALLOWED = 405, "Method Not Allowed" + NOT_ACCEPTABLE = 406, "Not Acceptable" + PROXY_AUTHENTICATION_REQUIRED = 407, "Proxy Authentication Required" + REQUEST_TIMEOUT = 408, "Request Timeout" + CONFLICT = 409, "Conflict" + GONE = 410, "Gone" + LENGTH_REQUIRED = 411, "Length Required" + PRECONDITION_FAILED = 412, "Precondition Failed" + REQUEST_ENTITY_TOO_LARGE = 413, "Request Entity Too Large" + REQUEST_URI_TOO_LONG = 414, "Request-URI Too Long" + UNSUPPORTED_MEDIA_TYPE = 415, "Unsupported Media Type" + REQUESTED_RANGE_NOT_SATISFIABLE = 416, "Requested Range Not Satisfiable" + EXPECTATION_FAILED = 417, "Expectation Failed" + IM_A_TEAPOT = 418, "I'm a teapot" + MISDIRECTED_REQUEST = 421, "Misdirected Request" + UNPROCESSABLE_ENTITY = 422, "Unprocessable Entity" + LOCKED = 423, "Locked" + FAILED_DEPENDENCY = 424, "Failed Dependency" + TOO_EARLY = 425, "Too Early" + UPGRADE_REQUIRED = 426, "Upgrade Required" + PRECONDITION_REQUIRED = 428, "Precondition Required" + TOO_MANY_REQUESTS = 429, "Too Many Requests" + REQUEST_HEADER_FIELDS_TOO_LARGE = 431, "Request Header Fields Too Large" + UNAVAILABLE_FOR_LEGAL_REASONS = 451, "Unavailable For Legal Reasons" + + # server errors + INTERNAL_SERVER_ERROR = 500, "Internal Server Error" + NOT_IMPLEMENTED = 501, "Not Implemented" + BAD_GATEWAY = 502, "Bad Gateway" + SERVICE_UNAVAILABLE = 503, "Service Unavailable" + GATEWAY_TIMEOUT = 504, "Gateway Timeout" + HTTP_VERSION_NOT_SUPPORTED = 505, "HTTP Version Not Supported" + VARIANT_ALSO_NEGOTIATES = 506, "Variant Also Negotiates" + INSUFFICIENT_STORAGE = 507, "Insufficient Storage" + LOOP_DETECTED = 508, "Loop Detected" + NOT_EXTENDED = 510, "Not Extended" + NETWORK_AUTHENTICATION_REQUIRED = 511, "Network Authentication Required" + + +# Include lower-case styles for `requests` compatibility. +for code in codes: + setattr(codes, code._name_.lower(), int(code)) diff --git a/.venv/Lib/site-packages/httpx/_transports/__init__.py b/.venv/Lib/site-packages/httpx/_transports/__init__.py new file mode 100644 index 0000000..7a32105 --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_transports/__init__.py @@ -0,0 +1,15 @@ +from .asgi import * +from .base import * +from .default import * +from .mock import * +from .wsgi import * + +__all__ = [ + "ASGITransport", + "AsyncBaseTransport", + "BaseTransport", + "AsyncHTTPTransport", + "HTTPTransport", + "MockTransport", + "WSGITransport", +] diff --git a/.venv/Lib/site-packages/httpx/_transports/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/httpx/_transports/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b71dcd91b7e224a26f6e42f1824104948126c0c GIT binary patch literal 460 zcmZ9I&r8EF6vvaU+M)Eai5D-uR&cuy8BE81fXZMKK>{IbCH{bX2lJ{)eTS&*FR~Z&DLa&)vhVcXQs)jq{BOgcT$-oQ3 zD>7D;TalU30Qtr}zTBAQT=Pxej$EwpzH2GF)@`+iN(;fYaY;LfpxAz>z_yrZe R*X{y7%>1@-Yo!J4e*w6#kMsZl literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/_transports/__pycache__/asgi.cpython-312.pyc b/.venv/Lib/site-packages/httpx/_transports/__pycache__/asgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..533687a6df84a8d5b2694b15e6d6c1f2d547c1ca GIT binary patch literal 7603 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/_transports/__pycache__/base.cpython-312.pyc b/.venv/Lib/site-packages/httpx/_transports/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5bc0e4d5250b96811b0daa6bb39aaf9b005111a7 GIT binary patch literal 3890 zcmb7G&2JmW6`$odSEA%da`R!;o(O3rx(emQMhh5`Q9G7n_k%EOra&16)N*$ut+m{x zXO@zv=Ab%8+n_CyQ`BRxg4KUwM ztgMr5=jyq3zMj`uiiy;{Qv+s4Un^(j|5SLCidO2yuca$m`Y}4)(yjb@sy>!tZ!l}@ zA+z!ckMVGA9BX>AR_dk7r7!4`vKEXPuIu@RZ+ot)VqU6A!xSyU+^qFF;tP_coC@-H z#Qm;Neo(k0R0pIY&}u>HnxO>fT`Wl-hqEoO>soul};}xHk-org={u=pB*w$CE1vSY$^}1#~x2k|7NTYlvT|*@MwZ4iuHR52K7vMS-F#Ifk4LsUhKx z?L+c!osc|SC!VSVu*VL^S+VrK{+|B(ad{dmW*pc+L&+oWJcr3X^Ap)E?NN+B(8?*J zhWmB8pT=F`THN5ucGn%jZ`NwJxr_pn8`zP;?@AX0G7i#;o*ca{wuIw#;AgHjysl%p z^FD{#wrMy{k7HMIR|t#y9;YR4yTJSnTXFKdWlKbY(_2g!LI_iUK=9Wc8}8&LB17?x z^tKQ?iW3jo-LTg;z}unwc^859@|s~n0b-jlb8S=jwou0ezU6XHS~v+Yz!Z{0Oeq|J zAcD3%Nb7HVa+8$xy3!PCF}!b}d%|1!QW))6YkpqXkV>BCEx}DBa<>hg?KYro_yP*@ zZ4khQp%Hv9VY515T!|P~z|~qPuP#DNE*uC&+OBTE5sqlaZdr}kL>5n2;p#QdalCC@ z9pC25-qM=whQ4Syp1DbC3y3GlRKz(}!&V6!!`U`^3U)^{TgNQ7vSYM6h>xy99$r2Q z0g9mTx-c^<6h2AuS}1cv@g!F-CzsG68c>KY^XOz;3>Qui5n)^S8=ke?ntNlVHg^;* zv^5xA=`1g_=7^b^TjZ^|?d|P~h@`HVUK?wdE?uOJrMdEG78@`=VtM&3@VV(Je~=OP~rxsCvK{bSz06BdE^Z1h$g9j+we?Ov#X z$^5Xc^Rf4MyQ_T45Ss#tE6)8P=kc#SP9(JN9Qc;x~l*@@>{=2io zMRR!f1sqf}0DJ7|6*f8dc>GE~oto4SS*oZ<6*Q@mprVnc_l+v_{-C}~>MP_k0MYvv zmTmbg=DX9wmxpbXLaBD*s~dxNbr;TF>FsK*#=y+rqAhyk9a7cr6aa7$X2^b*3*AwCbh zoDT}krfalCvl-~kX4|v6s5;GHyxF|pHJoTik}>5hfjI(Xg~&ei97UF)wjt~tCV;4$ z01sI|rDbOhb#~#>fN|0BTvx9Lfmpi#}wI2aB!KNN)T(p99^kMaCR4bY#{+gjKLj0_X9 zq%r7BzJ0QTLwhpVz6Q2x4B#K^-*JobpRiSUkj`queF|wEit5`$sG831MAx69NBlI0b_)}s>rVCYnS_q5H^`l}FyK;O&8y!?+ znMmgzOT02Nq~t3g-F<$9T#*CcBuOX-qc|#z$5*E^+Mo_mltq~zV&9?ZcL^*IC zIjRvlNcE{cYJ~pu)SwcSCn&wvx@b{Rs3c`YkfR3FT@SKUOqI;gTAqp-`l+b;QQ-^n zHEjRPkYN>;WCc()(JO&ZSieGD!or^vwxIA@R1~ks*KmLy4n@X3Olz9<8Jqi@l|EzV z{?6u~WTy6R{JQ$f>IWD9;C-5@>}ijWRz5lV>Zh5Dd)kw+bALSdiT=u7{>$72t@KX@ Q@a39zS}XmF0feRQzvCLjpa1{> literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/_transports/__pycache__/default.cpython-312.pyc b/.venv/Lib/site-packages/httpx/_transports/__pycache__/default.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fd90cd63cf40f8506be437f1bdd3d987c96119e GIT binary patch literal 17069 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/_transports/__pycache__/mock.cpython-312.pyc b/.venv/Lib/site-packages/httpx/_transports/__pycache__/mock.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd7311460ee2f3270b8ca5c299ff2243cb9fd5bd GIT binary patch literal 1958 zcmbtUO>7fK6rNe{de@GBLI@=MBy6claa2IGw3Sj+DGjA*Y15R@i)CrGcqd_#^{$y& z15OP@fT~2Lih990^uVEqps4qrDmeGzh)P5&sv>b{xmgO85U0MkUI!di?V)4&?VI=J zy_xsDZ|0YDI!R!x7KhI@WI`U}q&?y-g>L)|N}X85q7Et1Jf)~hj+B?Im?b;0lAM=I zN?rlGV#S?!NzJPzEw53+q?w*}x))z2PgAB$NxQ(5PyWjqX=dk(sJ3QFmITo;jE-`o zEOnaZ(-QfJSlSI@sZ3?sD%67`pQ%c@#1m9=H0Uy2*Yi!^_FOUkL_(t!sHfTa3KM>y zoo1p89`=N`JUt_#>J~pR1v|q{7ku0g(p#pVdBxdHZxQ!t4@?h(ZX5$yCyeB&MF2I$ zqBrDxEJ0d7D{e_JmtUI4V6Ifa3~ z`URn%bWAZ@7&D8rjO!=78Lx2o0&_1EKDEylgzd9KWpJ6(Occ)fe)-Z7!*4?+j+6kK z!{utA8HVlJzG2Mm?11ZV+un`mOMsXs>wSX@^0%oCm~>W-4Gk;50t!|>gI(G(R--v3 zH8KOMw~iWJjiN3~zS8#ABtN1%T!sj3(dFZT!eMPjU86N=5=b*X_r_?*CA}h;Zo0aO zOwl9UbsG$NXc={E=K47$kU2B0un|Gh7Pc#V(=9TN`v!?K)iV2#bC2JN@hn{`P|l z+QaOw<%2(r{hB@WuxC$W?-Cx*zFW~Wh;!ywxEsjhp35MhA2M^~J|9GZ49vyxj=E-v8AgyajFM+n9Mn^WalT?Y zEf3#@>vyBTdhd{s86hFVhbk0EU-34G8)QSG%D!e?Q~Dom>u)OXZN^EmXG2A;HxrrR z8=9Z0poJN8SvR}+hswj10U5eAOJSP$Ckf}0vX@BXxi}>7Vq;zV8gdVOo3>|D5bxV z{f|ld5Ayo&i}@6kpW4$%?wMDg#`n_xM(=@@^j`$D@Em^w7}3%t literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/_transports/__pycache__/wsgi.cpython-312.pyc b/.venv/Lib/site-packages/httpx/_transports/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ca76e3eb04e6507cae4d9c03def1c919531c08a GIT binary patch literal 6822 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 Y_NOlhG%s2ejvL@^pZ_nyI!w<03k(c|^Z)<= literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpx/_transports/asgi.py b/.venv/Lib/site-packages/httpx/_transports/asgi.py new file mode 100644 index 0000000..2bc4efa --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_transports/asgi.py @@ -0,0 +1,187 @@ +from __future__ import annotations + +import typing + +from .._models import Request, Response +from .._types import AsyncByteStream +from .base import AsyncBaseTransport + +if typing.TYPE_CHECKING: # pragma: no cover + import asyncio + + import trio + + Event = typing.Union[asyncio.Event, trio.Event] + + +_Message = typing.MutableMapping[str, typing.Any] +_Receive = typing.Callable[[], typing.Awaitable[_Message]] +_Send = typing.Callable[ + [typing.MutableMapping[str, typing.Any]], typing.Awaitable[None] +] +_ASGIApp = typing.Callable[ + [typing.MutableMapping[str, typing.Any], _Receive, _Send], typing.Awaitable[None] +] + +__all__ = ["ASGITransport"] + + +def is_running_trio() -> bool: + try: + # sniffio is a dependency of trio. + + # See https://github.com/python-trio/trio/issues/2802 + import sniffio + + if sniffio.current_async_library() == "trio": + return True + except ImportError: # pragma: nocover + pass + + return False + + +def create_event() -> Event: + if is_running_trio(): + import trio + + return trio.Event() + + import asyncio + + return asyncio.Event() + + +class ASGIResponseStream(AsyncByteStream): + def __init__(self, body: list[bytes]) -> None: + self._body = body + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + yield b"".join(self._body) + + +class ASGITransport(AsyncBaseTransport): + """ + A custom AsyncTransport that handles sending requests directly to an ASGI app. + + ```python + transport = httpx.ASGITransport( + app=app, + root_path="/submount", + client=("1.2.3.4", 123) + ) + client = httpx.AsyncClient(transport=transport) + ``` + + Arguments: + + * `app` - The ASGI application. + * `raise_app_exceptions` - Boolean indicating if exceptions in the application + should be raised. Default to `True`. Can be set to `False` for use cases + such as testing the content of a client 500 response. + * `root_path` - The root path on which the ASGI application should be mounted. + * `client` - A two-tuple indicating the client IP and port of incoming requests. + ``` + """ + + def __init__( + self, + app: _ASGIApp, + raise_app_exceptions: bool = True, + root_path: str = "", + client: tuple[str, int] = ("127.0.0.1", 123), + ) -> None: + self.app = app + self.raise_app_exceptions = raise_app_exceptions + self.root_path = root_path + self.client = client + + async def handle_async_request( + self, + request: Request, + ) -> Response: + assert isinstance(request.stream, AsyncByteStream) + + # ASGI scope. + scope = { + "type": "http", + "asgi": {"version": "3.0"}, + "http_version": "1.1", + "method": request.method, + "headers": [(k.lower(), v) for (k, v) in request.headers.raw], + "scheme": request.url.scheme, + "path": request.url.path, + "raw_path": request.url.raw_path.split(b"?")[0], + "query_string": request.url.query, + "server": (request.url.host, request.url.port), + "client": self.client, + "root_path": self.root_path, + } + + # Request. + request_body_chunks = request.stream.__aiter__() + request_complete = False + + # Response. + status_code = None + response_headers = None + body_parts = [] + response_started = False + response_complete = create_event() + + # ASGI callables. + + async def receive() -> dict[str, typing.Any]: + nonlocal request_complete + + if request_complete: + await response_complete.wait() + return {"type": "http.disconnect"} + + try: + body = await request_body_chunks.__anext__() + except StopAsyncIteration: + request_complete = True + return {"type": "http.request", "body": b"", "more_body": False} + return {"type": "http.request", "body": body, "more_body": True} + + async def send(message: typing.MutableMapping[str, typing.Any]) -> None: + nonlocal status_code, response_headers, response_started + + if message["type"] == "http.response.start": + assert not response_started + + status_code = message["status"] + response_headers = message.get("headers", []) + response_started = True + + elif message["type"] == "http.response.body": + assert not response_complete.is_set() + body = message.get("body", b"") + more_body = message.get("more_body", False) + + if body and request.method != "HEAD": + body_parts.append(body) + + if not more_body: + response_complete.set() + + try: + await self.app(scope, receive, send) + except Exception: # noqa: PIE-786 + if self.raise_app_exceptions: + raise + + response_complete.set() + if status_code is None: + status_code = 500 + if response_headers is None: + response_headers = {} + + assert response_complete.is_set() + assert status_code is not None + assert response_headers is not None + + stream = ASGIResponseStream(body_parts) + + return Response(status_code, headers=response_headers, stream=stream) diff --git a/.venv/Lib/site-packages/httpx/_transports/base.py b/.venv/Lib/site-packages/httpx/_transports/base.py new file mode 100644 index 0000000..66fd99d --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_transports/base.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import typing +from types import TracebackType + +from .._models import Request, Response + +T = typing.TypeVar("T", bound="BaseTransport") +A = typing.TypeVar("A", bound="AsyncBaseTransport") + +__all__ = ["AsyncBaseTransport", "BaseTransport"] + + +class BaseTransport: + def __enter__(self: T) -> T: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + self.close() + + def handle_request(self, request: Request) -> Response: + """ + Send a single HTTP request and return a response. + + Developers shouldn't typically ever need to call into this API directly, + since the Client class provides all the higher level user-facing API + niceties. + + In order to properly release any network resources, the response + stream should *either* be consumed immediately, with a call to + `response.stream.read()`, or else the `handle_request` call should + be followed with a try/finally block to ensuring the stream is + always closed. + + Example usage: + + with httpx.HTTPTransport() as transport: + req = httpx.Request( + method=b"GET", + url=(b"https", b"www.example.com", 443, b"/"), + headers=[(b"Host", b"www.example.com")], + ) + resp = transport.handle_request(req) + body = resp.stream.read() + print(resp.status_code, resp.headers, body) + + + Takes a `Request` instance as the only argument. + + Returns a `Response` instance. + """ + raise NotImplementedError( + "The 'handle_request' method must be implemented." + ) # pragma: no cover + + def close(self) -> None: + pass + + +class AsyncBaseTransport: + async def __aenter__(self: A) -> A: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + await self.aclose() + + async def handle_async_request( + self, + request: Request, + ) -> Response: + raise NotImplementedError( + "The 'handle_async_request' method must be implemented." + ) # pragma: no cover + + async def aclose(self) -> None: + pass diff --git a/.venv/Lib/site-packages/httpx/_transports/default.py b/.venv/Lib/site-packages/httpx/_transports/default.py new file mode 100644 index 0000000..d5aa05f --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_transports/default.py @@ -0,0 +1,406 @@ +""" +Custom transports, with nicely configured defaults. + +The following additional keyword arguments are currently supported by httpcore... + +* uds: str +* local_address: str +* retries: int + +Example usages... + +# Disable HTTP/2 on a single specific domain. +mounts = { + "all://": httpx.HTTPTransport(http2=True), + "all://*example.org": httpx.HTTPTransport() +} + +# Using advanced httpcore configuration, with connection retries. +transport = httpx.HTTPTransport(retries=1) +client = httpx.Client(transport=transport) + +# Using advanced httpcore configuration, with unix domain sockets. +transport = httpx.HTTPTransport(uds="socket.uds") +client = httpx.Client(transport=transport) +""" + +from __future__ import annotations + +import contextlib +import typing +from types import TracebackType + +if typing.TYPE_CHECKING: + import ssl # pragma: no cover + + import httpx # pragma: no cover + +from .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context +from .._exceptions import ( + ConnectError, + ConnectTimeout, + LocalProtocolError, + NetworkError, + PoolTimeout, + ProtocolError, + ProxyError, + ReadError, + ReadTimeout, + RemoteProtocolError, + TimeoutException, + UnsupportedProtocol, + WriteError, + WriteTimeout, +) +from .._models import Request, Response +from .._types import AsyncByteStream, CertTypes, ProxyTypes, SyncByteStream +from .._urls import URL +from .base import AsyncBaseTransport, BaseTransport + +T = typing.TypeVar("T", bound="HTTPTransport") +A = typing.TypeVar("A", bound="AsyncHTTPTransport") + +SOCKET_OPTION = typing.Union[ + typing.Tuple[int, int, int], + typing.Tuple[int, int, typing.Union[bytes, bytearray]], + typing.Tuple[int, int, None, int], +] + +__all__ = ["AsyncHTTPTransport", "HTTPTransport"] + +HTTPCORE_EXC_MAP: dict[type[Exception], type[httpx.HTTPError]] = {} + + +def _load_httpcore_exceptions() -> dict[type[Exception], type[httpx.HTTPError]]: + import httpcore + + return { + httpcore.TimeoutException: TimeoutException, + httpcore.ConnectTimeout: ConnectTimeout, + httpcore.ReadTimeout: ReadTimeout, + httpcore.WriteTimeout: WriteTimeout, + httpcore.PoolTimeout: PoolTimeout, + httpcore.NetworkError: NetworkError, + httpcore.ConnectError: ConnectError, + httpcore.ReadError: ReadError, + httpcore.WriteError: WriteError, + httpcore.ProxyError: ProxyError, + httpcore.UnsupportedProtocol: UnsupportedProtocol, + httpcore.ProtocolError: ProtocolError, + httpcore.LocalProtocolError: LocalProtocolError, + httpcore.RemoteProtocolError: RemoteProtocolError, + } + + +@contextlib.contextmanager +def map_httpcore_exceptions() -> typing.Iterator[None]: + global HTTPCORE_EXC_MAP + if len(HTTPCORE_EXC_MAP) == 0: + HTTPCORE_EXC_MAP = _load_httpcore_exceptions() + try: + yield + except Exception as exc: + mapped_exc = None + + for from_exc, to_exc in HTTPCORE_EXC_MAP.items(): + if not isinstance(exc, from_exc): + continue + # We want to map to the most specific exception we can find. + # Eg if `exc` is an `httpcore.ReadTimeout`, we want to map to + # `httpx.ReadTimeout`, not just `httpx.TimeoutException`. + if mapped_exc is None or issubclass(to_exc, mapped_exc): + mapped_exc = to_exc + + if mapped_exc is None: # pragma: no cover + raise + + message = str(exc) + raise mapped_exc(message) from exc + + +class ResponseStream(SyncByteStream): + def __init__(self, httpcore_stream: typing.Iterable[bytes]) -> None: + self._httpcore_stream = httpcore_stream + + def __iter__(self) -> typing.Iterator[bytes]: + with map_httpcore_exceptions(): + for part in self._httpcore_stream: + yield part + + def close(self) -> None: + if hasattr(self._httpcore_stream, "close"): + self._httpcore_stream.close() + + +class HTTPTransport(BaseTransport): + def __init__( + self, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + proxy: ProxyTypes | None = None, + uds: str | None = None, + local_address: str | None = None, + retries: int = 0, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + import httpcore + + proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy + ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env) + + if proxy is None: + self._pool = httpcore.ConnectionPool( + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + uds=uds, + local_address=local_address, + retries=retries, + socket_options=socket_options, + ) + elif proxy.url.scheme in ("http", "https"): + self._pool = httpcore.HTTPProxy( + proxy_url=httpcore.URL( + scheme=proxy.url.raw_scheme, + host=proxy.url.raw_host, + port=proxy.url.port, + target=proxy.url.raw_path, + ), + proxy_auth=proxy.raw_auth, + proxy_headers=proxy.headers.raw, + ssl_context=ssl_context, + proxy_ssl_context=proxy.ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + socket_options=socket_options, + ) + elif proxy.url.scheme in ("socks5", "socks5h"): + try: + import socksio # noqa + except ImportError: # pragma: no cover + raise ImportError( + "Using SOCKS proxy, but the 'socksio' package is not installed. " + "Make sure to install httpx using `pip install httpx[socks]`." + ) from None + + self._pool = httpcore.SOCKSProxy( + proxy_url=httpcore.URL( + scheme=proxy.url.raw_scheme, + host=proxy.url.raw_host, + port=proxy.url.port, + target=proxy.url.raw_path, + ), + proxy_auth=proxy.raw_auth, + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + ) + else: # pragma: no cover + raise ValueError( + "Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h'," + f" but got {proxy.url.scheme!r}." + ) + + def __enter__(self: T) -> T: # Use generics for subclass support. + self._pool.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + with map_httpcore_exceptions(): + self._pool.__exit__(exc_type, exc_value, traceback) + + def handle_request( + self, + request: Request, + ) -> Response: + assert isinstance(request.stream, SyncByteStream) + import httpcore + + req = httpcore.Request( + method=request.method, + url=httpcore.URL( + scheme=request.url.raw_scheme, + host=request.url.raw_host, + port=request.url.port, + target=request.url.raw_path, + ), + headers=request.headers.raw, + content=request.stream, + extensions=request.extensions, + ) + with map_httpcore_exceptions(): + resp = self._pool.handle_request(req) + + assert isinstance(resp.stream, typing.Iterable) + + return Response( + status_code=resp.status, + headers=resp.headers, + stream=ResponseStream(resp.stream), + extensions=resp.extensions, + ) + + def close(self) -> None: + self._pool.close() + + +class AsyncResponseStream(AsyncByteStream): + def __init__(self, httpcore_stream: typing.AsyncIterable[bytes]) -> None: + self._httpcore_stream = httpcore_stream + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + with map_httpcore_exceptions(): + async for part in self._httpcore_stream: + yield part + + async def aclose(self) -> None: + if hasattr(self._httpcore_stream, "aclose"): + await self._httpcore_stream.aclose() + + +class AsyncHTTPTransport(AsyncBaseTransport): + def __init__( + self, + verify: ssl.SSLContext | str | bool = True, + cert: CertTypes | None = None, + trust_env: bool = True, + http1: bool = True, + http2: bool = False, + limits: Limits = DEFAULT_LIMITS, + proxy: ProxyTypes | None = None, + uds: str | None = None, + local_address: str | None = None, + retries: int = 0, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + import httpcore + + proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy + ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env) + + if proxy is None: + self._pool = httpcore.AsyncConnectionPool( + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + uds=uds, + local_address=local_address, + retries=retries, + socket_options=socket_options, + ) + elif proxy.url.scheme in ("http", "https"): + self._pool = httpcore.AsyncHTTPProxy( + proxy_url=httpcore.URL( + scheme=proxy.url.raw_scheme, + host=proxy.url.raw_host, + port=proxy.url.port, + target=proxy.url.raw_path, + ), + proxy_auth=proxy.raw_auth, + proxy_headers=proxy.headers.raw, + proxy_ssl_context=proxy.ssl_context, + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + socket_options=socket_options, + ) + elif proxy.url.scheme in ("socks5", "socks5h"): + try: + import socksio # noqa + except ImportError: # pragma: no cover + raise ImportError( + "Using SOCKS proxy, but the 'socksio' package is not installed. " + "Make sure to install httpx using `pip install httpx[socks]`." + ) from None + + self._pool = httpcore.AsyncSOCKSProxy( + proxy_url=httpcore.URL( + scheme=proxy.url.raw_scheme, + host=proxy.url.raw_host, + port=proxy.url.port, + target=proxy.url.raw_path, + ), + proxy_auth=proxy.raw_auth, + ssl_context=ssl_context, + max_connections=limits.max_connections, + max_keepalive_connections=limits.max_keepalive_connections, + keepalive_expiry=limits.keepalive_expiry, + http1=http1, + http2=http2, + ) + else: # pragma: no cover + raise ValueError( + "Proxy protocol must be either 'http', 'https', 'socks5', or 'socks5h'," + " but got {proxy.url.scheme!r}." + ) + + async def __aenter__(self: A) -> A: # Use generics for subclass support. + await self._pool.__aenter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: TracebackType | None = None, + ) -> None: + with map_httpcore_exceptions(): + await self._pool.__aexit__(exc_type, exc_value, traceback) + + async def handle_async_request( + self, + request: Request, + ) -> Response: + assert isinstance(request.stream, AsyncByteStream) + import httpcore + + req = httpcore.Request( + method=request.method, + url=httpcore.URL( + scheme=request.url.raw_scheme, + host=request.url.raw_host, + port=request.url.port, + target=request.url.raw_path, + ), + headers=request.headers.raw, + content=request.stream, + extensions=request.extensions, + ) + with map_httpcore_exceptions(): + resp = await self._pool.handle_async_request(req) + + assert isinstance(resp.stream, typing.AsyncIterable) + + return Response( + status_code=resp.status, + headers=resp.headers, + stream=AsyncResponseStream(resp.stream), + extensions=resp.extensions, + ) + + async def aclose(self) -> None: + await self._pool.aclose() diff --git a/.venv/Lib/site-packages/httpx/_transports/mock.py b/.venv/Lib/site-packages/httpx/_transports/mock.py new file mode 100644 index 0000000..8c418f5 --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_transports/mock.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import typing + +from .._models import Request, Response +from .base import AsyncBaseTransport, BaseTransport + +SyncHandler = typing.Callable[[Request], Response] +AsyncHandler = typing.Callable[[Request], typing.Coroutine[None, None, Response]] + + +__all__ = ["MockTransport"] + + +class MockTransport(AsyncBaseTransport, BaseTransport): + def __init__(self, handler: SyncHandler | AsyncHandler) -> None: + self.handler = handler + + def handle_request( + self, + request: Request, + ) -> Response: + request.read() + response = self.handler(request) + if not isinstance(response, Response): # pragma: no cover + raise TypeError("Cannot use an async handler in a sync Client") + return response + + async def handle_async_request( + self, + request: Request, + ) -> Response: + await request.aread() + response = self.handler(request) + + # Allow handler to *optionally* be an `async` function. + # If it is, then the `response` variable need to be awaited to actually + # return the result. + + if not isinstance(response, Response): + response = await response + + return response diff --git a/.venv/Lib/site-packages/httpx/_transports/wsgi.py b/.venv/Lib/site-packages/httpx/_transports/wsgi.py new file mode 100644 index 0000000..8592ffe --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_transports/wsgi.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +import io +import itertools +import sys +import typing + +from .._models import Request, Response +from .._types import SyncByteStream +from .base import BaseTransport + +if typing.TYPE_CHECKING: + from _typeshed import OptExcInfo # pragma: no cover + from _typeshed.wsgi import WSGIApplication # pragma: no cover + +_T = typing.TypeVar("_T") + + +__all__ = ["WSGITransport"] + + +def _skip_leading_empty_chunks(body: typing.Iterable[_T]) -> typing.Iterable[_T]: + body = iter(body) + for chunk in body: + if chunk: + return itertools.chain([chunk], body) + return [] + + +class WSGIByteStream(SyncByteStream): + def __init__(self, result: typing.Iterable[bytes]) -> None: + self._close = getattr(result, "close", None) + self._result = _skip_leading_empty_chunks(result) + + def __iter__(self) -> typing.Iterator[bytes]: + for part in self._result: + yield part + + def close(self) -> None: + if self._close is not None: + self._close() + + +class WSGITransport(BaseTransport): + """ + A custom transport that handles sending requests directly to an WSGI app. + The simplest way to use this functionality is to use the `app` argument. + + ``` + client = httpx.Client(app=app) + ``` + + Alternatively, you can setup the transport instance explicitly. + This allows you to include any additional configuration arguments specific + to the WSGITransport class: + + ``` + transport = httpx.WSGITransport( + app=app, + script_name="/submount", + remote_addr="1.2.3.4" + ) + client = httpx.Client(transport=transport) + ``` + + Arguments: + + * `app` - The WSGI application. + * `raise_app_exceptions` - Boolean indicating if exceptions in the application + should be raised. Default to `True`. Can be set to `False` for use cases + such as testing the content of a client 500 response. + * `script_name` - The root path on which the WSGI application should be mounted. + * `remote_addr` - A string indicating the client IP of incoming requests. + ``` + """ + + def __init__( + self, + app: WSGIApplication, + raise_app_exceptions: bool = True, + script_name: str = "", + remote_addr: str = "127.0.0.1", + wsgi_errors: typing.TextIO | None = None, + ) -> None: + self.app = app + self.raise_app_exceptions = raise_app_exceptions + self.script_name = script_name + self.remote_addr = remote_addr + self.wsgi_errors = wsgi_errors + + def handle_request(self, request: Request) -> Response: + request.read() + wsgi_input = io.BytesIO(request.content) + + port = request.url.port or {"http": 80, "https": 443}[request.url.scheme] + environ = { + "wsgi.version": (1, 0), + "wsgi.url_scheme": request.url.scheme, + "wsgi.input": wsgi_input, + "wsgi.errors": self.wsgi_errors or sys.stderr, + "wsgi.multithread": True, + "wsgi.multiprocess": False, + "wsgi.run_once": False, + "REQUEST_METHOD": request.method, + "SCRIPT_NAME": self.script_name, + "PATH_INFO": request.url.path, + "QUERY_STRING": request.url.query.decode("ascii"), + "SERVER_NAME": request.url.host, + "SERVER_PORT": str(port), + "SERVER_PROTOCOL": "HTTP/1.1", + "REMOTE_ADDR": self.remote_addr, + } + for header_key, header_value in request.headers.raw: + key = header_key.decode("ascii").upper().replace("-", "_") + if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"): + key = "HTTP_" + key + environ[key] = header_value.decode("ascii") + + seen_status = None + seen_response_headers = None + seen_exc_info = None + + def start_response( + status: str, + response_headers: list[tuple[str, str]], + exc_info: OptExcInfo | None = None, + ) -> typing.Callable[[bytes], typing.Any]: + nonlocal seen_status, seen_response_headers, seen_exc_info + seen_status = status + seen_response_headers = response_headers + seen_exc_info = exc_info + return lambda _: None + + result = self.app(environ, start_response) + + stream = WSGIByteStream(result) + + assert seen_status is not None + assert seen_response_headers is not None + if seen_exc_info and seen_exc_info[0] and self.raise_app_exceptions: + raise seen_exc_info[1] + + status_code = int(seen_status.split()[0]) + headers = [ + (key.encode("ascii"), value.encode("ascii")) + for key, value in seen_response_headers + ] + + return Response(status_code, headers=headers, stream=stream) diff --git a/.venv/Lib/site-packages/httpx/_types.py b/.venv/Lib/site-packages/httpx/_types.py new file mode 100644 index 0000000..704dfdf --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_types.py @@ -0,0 +1,114 @@ +""" +Type definitions for type checking purposes. +""" + +from http.cookiejar import CookieJar +from typing import ( + IO, + TYPE_CHECKING, + Any, + AsyncIterable, + AsyncIterator, + Callable, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Sequence, + Tuple, + Union, +) + +if TYPE_CHECKING: # pragma: no cover + from ._auth import Auth # noqa: F401 + from ._config import Proxy, Timeout # noqa: F401 + from ._models import Cookies, Headers, Request # noqa: F401 + from ._urls import URL, QueryParams # noqa: F401 + + +PrimitiveData = Optional[Union[str, int, float, bool]] + +URLTypes = Union["URL", str] + +QueryParamTypes = Union[ + "QueryParams", + Mapping[str, Union[PrimitiveData, Sequence[PrimitiveData]]], + List[Tuple[str, PrimitiveData]], + Tuple[Tuple[str, PrimitiveData], ...], + str, + bytes, +] + +HeaderTypes = Union[ + "Headers", + Mapping[str, str], + Mapping[bytes, bytes], + Sequence[Tuple[str, str]], + Sequence[Tuple[bytes, bytes]], +] + +CookieTypes = Union["Cookies", CookieJar, Dict[str, str], List[Tuple[str, str]]] + +TimeoutTypes = Union[ + Optional[float], + Tuple[Optional[float], Optional[float], Optional[float], Optional[float]], + "Timeout", +] +ProxyTypes = Union["URL", str, "Proxy"] +CertTypes = Union[str, Tuple[str, str], Tuple[str, str, str]] + +AuthTypes = Union[ + Tuple[Union[str, bytes], Union[str, bytes]], + Callable[["Request"], "Request"], + "Auth", +] + +RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] +ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] +ResponseExtensions = Mapping[str, Any] + +RequestData = Mapping[str, Any] + +FileContent = Union[IO[bytes], bytes, str] +FileTypes = Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + Tuple[Optional[str], FileContent], + # (filename, file (or bytes), content_type) + Tuple[Optional[str], FileContent, Optional[str]], + # (filename, file (or bytes), content_type, headers) + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], +] +RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]] + +RequestExtensions = Mapping[str, Any] + +__all__ = ["AsyncByteStream", "SyncByteStream"] + + +class SyncByteStream: + def __iter__(self) -> Iterator[bytes]: + raise NotImplementedError( + "The '__iter__' method must be implemented." + ) # pragma: no cover + yield b"" # pragma: no cover + + def close(self) -> None: + """ + Subclasses can override this method to release any network resources + after a request/response cycle is complete. + """ + + +class AsyncByteStream: + async def __aiter__(self) -> AsyncIterator[bytes]: + raise NotImplementedError( + "The '__aiter__' method must be implemented." + ) # pragma: no cover + yield b"" # pragma: no cover + + async def aclose(self) -> None: + pass diff --git a/.venv/Lib/site-packages/httpx/_urlparse.py b/.venv/Lib/site-packages/httpx/_urlparse.py new file mode 100644 index 0000000..bf190fd --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_urlparse.py @@ -0,0 +1,527 @@ +""" +An implementation of `urlparse` that provides URL validation and normalization +as described by RFC3986. + +We rely on this implementation rather than the one in Python's stdlib, because: + +* It provides more complete URL validation. +* It properly differentiates between an empty querystring and an absent querystring, + to distinguish URLs with a trailing '?'. +* It handles scheme, hostname, port, and path normalization. +* It supports IDNA hostnames, normalizing them to their encoded form. +* The API supports passing individual components, as well as the complete URL string. + +Previously we relied on the excellent `rfc3986` package to handle URL parsing and +validation, but this module provides a simpler alternative, with less indirection +required. +""" + +from __future__ import annotations + +import ipaddress +import re +import typing + +import idna + +from ._exceptions import InvalidURL + +MAX_URL_LENGTH = 65536 + +# https://datatracker.ietf.org/doc/html/rfc3986.html#section-2.3 +UNRESERVED_CHARACTERS = ( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" +) +SUB_DELIMS = "!$&'()*+,;=" + +PERCENT_ENCODED_REGEX = re.compile("%[A-Fa-f0-9]{2}") + +# https://url.spec.whatwg.org/#percent-encoded-bytes + +# The fragment percent-encode set is the C0 control percent-encode set +# and U+0020 SPACE, U+0022 ("), U+003C (<), U+003E (>), and U+0060 (`). +FRAG_SAFE = "".join( + [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x3C, 0x3E, 0x60)] +) + +# The query percent-encode set is the C0 control percent-encode set +# and U+0020 SPACE, U+0022 ("), U+0023 (#), U+003C (<), and U+003E (>). +QUERY_SAFE = "".join( + [chr(i) for i in range(0x20, 0x7F) if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E)] +) + +# The path percent-encode set is the query percent-encode set +# and U+003F (?), U+0060 (`), U+007B ({), and U+007D (}). +PATH_SAFE = "".join( + [ + chr(i) + for i in range(0x20, 0x7F) + if i not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + (0x3F, 0x60, 0x7B, 0x7D) + ] +) + +# The userinfo percent-encode set is the path percent-encode set +# and U+002F (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@), +# U+005B ([) to U+005E (^), inclusive, and U+007C (|). +USERNAME_SAFE = "".join( + [ + chr(i) + for i in range(0x20, 0x7F) + if i + not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + + (0x3F, 0x60, 0x7B, 0x7D) + + (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C) + ] +) +PASSWORD_SAFE = "".join( + [ + chr(i) + for i in range(0x20, 0x7F) + if i + not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + + (0x3F, 0x60, 0x7B, 0x7D) + + (0x2F, 0x3A, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C) + ] +) +# Note... The terminology 'userinfo' percent-encode set in the WHATWG document +# is used for the username and password quoting. For the joint userinfo component +# we remove U+003A (:) from the safe set. +USERINFO_SAFE = "".join( + [ + chr(i) + for i in range(0x20, 0x7F) + if i + not in (0x20, 0x22, 0x23, 0x3C, 0x3E) + + (0x3F, 0x60, 0x7B, 0x7D) + + (0x2F, 0x3B, 0x3D, 0x40, 0x5B, 0x5C, 0x5D, 0x5E, 0x7C) + ] +) + + +# {scheme}: (optional) +# //{authority} (optional) +# {path} +# ?{query} (optional) +# #{fragment} (optional) +URL_REGEX = re.compile( + ( + r"(?:(?P{scheme}):)?" + r"(?://(?P{authority}))?" + r"(?P{path})" + r"(?:\?(?P{query}))?" + r"(?:#(?P{fragment}))?" + ).format( + scheme="([a-zA-Z][a-zA-Z0-9+.-]*)?", + authority="[^/?#]*", + path="[^?#]*", + query="[^#]*", + fragment=".*", + ) +) + +# {userinfo}@ (optional) +# {host} +# :{port} (optional) +AUTHORITY_REGEX = re.compile( + ( + r"(?:(?P{userinfo})@)?" r"(?P{host})" r":?(?P{port})?" + ).format( + userinfo=".*", # Any character sequence. + host="(\\[.*\\]|[^:@]*)", # Either any character sequence excluding ':' or '@', + # or an IPv6 address enclosed within square brackets. + port=".*", # Any character sequence. + ) +) + + +# If we call urlparse with an individual component, then we need to regex +# validate that component individually. +# Note that we're duplicating the same strings as above. Shock! Horror!! +COMPONENT_REGEX = { + "scheme": re.compile("([a-zA-Z][a-zA-Z0-9+.-]*)?"), + "authority": re.compile("[^/?#]*"), + "path": re.compile("[^?#]*"), + "query": re.compile("[^#]*"), + "fragment": re.compile(".*"), + "userinfo": re.compile("[^@]*"), + "host": re.compile("(\\[.*\\]|[^:]*)"), + "port": re.compile(".*"), +} + + +# We use these simple regexs as a first pass before handing off to +# the stdlib 'ipaddress' module for IP address validation. +IPv4_STYLE_HOSTNAME = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$") +IPv6_STYLE_HOSTNAME = re.compile(r"^\[.*\]$") + + +class ParseResult(typing.NamedTuple): + scheme: str + userinfo: str + host: str + port: int | None + path: str + query: str | None + fragment: str | None + + @property + def authority(self) -> str: + return "".join( + [ + f"{self.userinfo}@" if self.userinfo else "", + f"[{self.host}]" if ":" in self.host else self.host, + f":{self.port}" if self.port is not None else "", + ] + ) + + @property + def netloc(self) -> str: + return "".join( + [ + f"[{self.host}]" if ":" in self.host else self.host, + f":{self.port}" if self.port is not None else "", + ] + ) + + def copy_with(self, **kwargs: str | None) -> ParseResult: + if not kwargs: + return self + + defaults = { + "scheme": self.scheme, + "authority": self.authority, + "path": self.path, + "query": self.query, + "fragment": self.fragment, + } + defaults.update(kwargs) + return urlparse("", **defaults) + + def __str__(self) -> str: + authority = self.authority + return "".join( + [ + f"{self.scheme}:" if self.scheme else "", + f"//{authority}" if authority else "", + self.path, + f"?{self.query}" if self.query is not None else "", + f"#{self.fragment}" if self.fragment is not None else "", + ] + ) + + +def urlparse(url: str = "", **kwargs: str | None) -> ParseResult: + # Initial basic checks on allowable URLs. + # --------------------------------------- + + # Hard limit the maximum allowable URL length. + if len(url) > MAX_URL_LENGTH: + raise InvalidURL("URL too long") + + # If a URL includes any ASCII control characters including \t, \r, \n, + # then treat it as invalid. + if any(char.isascii() and not char.isprintable() for char in url): + char = next(char for char in url if char.isascii() and not char.isprintable()) + idx = url.find(char) + error = ( + f"Invalid non-printable ASCII character in URL, {char!r} at position {idx}." + ) + raise InvalidURL(error) + + # Some keyword arguments require special handling. + # ------------------------------------------------ + + # Coerce "port" to a string, if it is provided as an integer. + if "port" in kwargs: + port = kwargs["port"] + kwargs["port"] = str(port) if isinstance(port, int) else port + + # Replace "netloc" with "host and "port". + if "netloc" in kwargs: + netloc = kwargs.pop("netloc") or "" + kwargs["host"], _, kwargs["port"] = netloc.partition(":") + + # Replace "username" and/or "password" with "userinfo". + if "username" in kwargs or "password" in kwargs: + username = quote(kwargs.pop("username", "") or "", safe=USERNAME_SAFE) + password = quote(kwargs.pop("password", "") or "", safe=PASSWORD_SAFE) + kwargs["userinfo"] = f"{username}:{password}" if password else username + + # Replace "raw_path" with "path" and "query". + if "raw_path" in kwargs: + raw_path = kwargs.pop("raw_path") or "" + kwargs["path"], seperator, kwargs["query"] = raw_path.partition("?") + if not seperator: + kwargs["query"] = None + + # Ensure that IPv6 "host" addresses are always escaped with "[...]". + if "host" in kwargs: + host = kwargs.get("host") or "" + if ":" in host and not (host.startswith("[") and host.endswith("]")): + kwargs["host"] = f"[{host}]" + + # If any keyword arguments are provided, ensure they are valid. + # ------------------------------------------------------------- + + for key, value in kwargs.items(): + if value is not None: + if len(value) > MAX_URL_LENGTH: + raise InvalidURL(f"URL component '{key}' too long") + + # If a component includes any ASCII control characters including \t, \r, \n, + # then treat it as invalid. + if any(char.isascii() and not char.isprintable() for char in value): + char = next( + char for char in value if char.isascii() and not char.isprintable() + ) + idx = value.find(char) + error = ( + f"Invalid non-printable ASCII character in URL {key} component, " + f"{char!r} at position {idx}." + ) + raise InvalidURL(error) + + # Ensure that keyword arguments match as a valid regex. + if not COMPONENT_REGEX[key].fullmatch(value): + raise InvalidURL(f"Invalid URL component '{key}'") + + # The URL_REGEX will always match, but may have empty components. + url_match = URL_REGEX.match(url) + assert url_match is not None + url_dict = url_match.groupdict() + + # * 'scheme', 'authority', and 'path' may be empty strings. + # * 'query' may be 'None', indicating no trailing "?" portion. + # Any string including the empty string, indicates a trailing "?". + # * 'fragment' may be 'None', indicating no trailing "#" portion. + # Any string including the empty string, indicates a trailing "#". + scheme = kwargs.get("scheme", url_dict["scheme"]) or "" + authority = kwargs.get("authority", url_dict["authority"]) or "" + path = kwargs.get("path", url_dict["path"]) or "" + query = kwargs.get("query", url_dict["query"]) + frag = kwargs.get("fragment", url_dict["fragment"]) + + # The AUTHORITY_REGEX will always match, but may have empty components. + authority_match = AUTHORITY_REGEX.match(authority) + assert authority_match is not None + authority_dict = authority_match.groupdict() + + # * 'userinfo' and 'host' may be empty strings. + # * 'port' may be 'None'. + userinfo = kwargs.get("userinfo", authority_dict["userinfo"]) or "" + host = kwargs.get("host", authority_dict["host"]) or "" + port = kwargs.get("port", authority_dict["port"]) + + # Normalize and validate each component. + # We end up with a parsed representation of the URL, + # with components that are plain ASCII bytestrings. + parsed_scheme: str = scheme.lower() + parsed_userinfo: str = quote(userinfo, safe=USERINFO_SAFE) + parsed_host: str = encode_host(host) + parsed_port: int | None = normalize_port(port, scheme) + + has_scheme = parsed_scheme != "" + has_authority = ( + parsed_userinfo != "" or parsed_host != "" or parsed_port is not None + ) + validate_path(path, has_scheme=has_scheme, has_authority=has_authority) + if has_scheme or has_authority: + path = normalize_path(path) + + parsed_path: str = quote(path, safe=PATH_SAFE) + parsed_query: str | None = None if query is None else quote(query, safe=QUERY_SAFE) + parsed_frag: str | None = None if frag is None else quote(frag, safe=FRAG_SAFE) + + # The parsed ASCII bytestrings are our canonical form. + # All properties of the URL are derived from these. + return ParseResult( + parsed_scheme, + parsed_userinfo, + parsed_host, + parsed_port, + parsed_path, + parsed_query, + parsed_frag, + ) + + +def encode_host(host: str) -> str: + if not host: + return "" + + elif IPv4_STYLE_HOSTNAME.match(host): + # Validate IPv4 hostnames like #.#.#.# + # + # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 + # + # IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + try: + ipaddress.IPv4Address(host) + except ipaddress.AddressValueError: + raise InvalidURL(f"Invalid IPv4 address: {host!r}") + return host + + elif IPv6_STYLE_HOSTNAME.match(host): + # Validate IPv6 hostnames like [...] + # + # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 + # + # "A host identified by an Internet Protocol literal address, version 6 + # [RFC3513] or later, is distinguished by enclosing the IP literal + # within square brackets ("[" and "]"). This is the only place where + # square bracket characters are allowed in the URI syntax." + try: + ipaddress.IPv6Address(host[1:-1]) + except ipaddress.AddressValueError: + raise InvalidURL(f"Invalid IPv6 address: {host!r}") + return host[1:-1] + + elif host.isascii(): + # Regular ASCII hostnames + # + # From https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.2 + # + # reg-name = *( unreserved / pct-encoded / sub-delims ) + WHATWG_SAFE = '"`{}%|\\' + return quote(host.lower(), safe=SUB_DELIMS + WHATWG_SAFE) + + # IDNA hostnames + try: + return idna.encode(host.lower()).decode("ascii") + except idna.IDNAError: + raise InvalidURL(f"Invalid IDNA hostname: {host!r}") + + +def normalize_port(port: str | int | None, scheme: str) -> int | None: + # From https://tools.ietf.org/html/rfc3986#section-3.2.3 + # + # "A scheme may define a default port. For example, the "http" scheme + # defines a default port of "80", corresponding to its reserved TCP + # port number. The type of port designated by the port number (e.g., + # TCP, UDP, SCTP) is defined by the URI scheme. URI producers and + # normalizers should omit the port component and its ":" delimiter if + # port is empty or if its value would be the same as that of the + # scheme's default." + if port is None or port == "": + return None + + try: + port_as_int = int(port) + except ValueError: + raise InvalidURL(f"Invalid port: {port!r}") + + # See https://url.spec.whatwg.org/#url-miscellaneous + default_port = {"ftp": 21, "http": 80, "https": 443, "ws": 80, "wss": 443}.get( + scheme + ) + if port_as_int == default_port: + return None + return port_as_int + + +def validate_path(path: str, has_scheme: bool, has_authority: bool) -> None: + """ + Path validation rules that depend on if the URL contains + a scheme or authority component. + + See https://datatracker.ietf.org/doc/html/rfc3986.html#section-3.3 + """ + if has_authority: + # If a URI contains an authority component, then the path component + # must either be empty or begin with a slash ("/") character." + if path and not path.startswith("/"): + raise InvalidURL("For absolute URLs, path must be empty or begin with '/'") + + if not has_scheme and not has_authority: + # If a URI does not contain an authority component, then the path cannot begin + # with two slash characters ("//"). + if path.startswith("//"): + raise InvalidURL("Relative URLs cannot have a path starting with '//'") + + # In addition, a URI reference (Section 4.1) may be a relative-path reference, + # in which case the first path segment cannot contain a colon (":") character. + if path.startswith(":"): + raise InvalidURL("Relative URLs cannot have a path starting with ':'") + + +def normalize_path(path: str) -> str: + """ + Drop "." and ".." segments from a URL path. + + For example: + + normalize_path("/path/./to/somewhere/..") == "/path/to" + """ + # Fast return when no '.' characters in the path. + if "." not in path: + return path + + components = path.split("/") + + # Fast return when no '.' or '..' components in the path. + if "." not in components and ".." not in components: + return path + + # https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4 + output: list[str] = [] + for component in components: + if component == ".": + pass + elif component == "..": + if output and output != [""]: + output.pop() + else: + output.append(component) + return "/".join(output) + + +def PERCENT(string: str) -> str: + return "".join([f"%{byte:02X}" for byte in string.encode("utf-8")]) + + +def percent_encoded(string: str, safe: str) -> str: + """ + Use percent-encoding to quote a string. + """ + NON_ESCAPED_CHARS = UNRESERVED_CHARACTERS + safe + + # Fast path for strings that don't need escaping. + if not string.rstrip(NON_ESCAPED_CHARS): + return string + + return "".join( + [char if char in NON_ESCAPED_CHARS else PERCENT(char) for char in string] + ) + + +def quote(string: str, safe: str) -> str: + """ + Use percent-encoding to quote a string, omitting existing '%xx' escape sequences. + + See: https://www.rfc-editor.org/rfc/rfc3986#section-2.1 + + * `string`: The string to be percent-escaped. + * `safe`: A string containing characters that may be treated as safe, and do not + need to be escaped. Unreserved characters are always treated as safe. + See: https://www.rfc-editor.org/rfc/rfc3986#section-2.3 + """ + parts = [] + current_position = 0 + for match in re.finditer(PERCENT_ENCODED_REGEX, string): + start_position, end_position = match.start(), match.end() + matched_text = match.group(0) + # Add any text up to the '%xx' escape sequence. + if start_position != current_position: + leading_text = string[current_position:start_position] + parts.append(percent_encoded(leading_text, safe=safe)) + + # Add the '%xx' escape sequence. + parts.append(matched_text) + current_position = end_position + + # Add any text after the final '%xx' escape sequence. + if current_position != len(string): + trailing_text = string[current_position:] + parts.append(percent_encoded(trailing_text, safe=safe)) + + return "".join(parts) diff --git a/.venv/Lib/site-packages/httpx/_urls.py b/.venv/Lib/site-packages/httpx/_urls.py new file mode 100644 index 0000000..147a8fa --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_urls.py @@ -0,0 +1,641 @@ +from __future__ import annotations + +import typing +from urllib.parse import parse_qs, unquote, urlencode + +import idna + +from ._types import QueryParamTypes +from ._urlparse import urlparse +from ._utils import primitive_value_to_str + +__all__ = ["URL", "QueryParams"] + + +class URL: + """ + url = httpx.URL("HTTPS://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink") + + assert url.scheme == "https" + assert url.username == "jo@email.com" + assert url.password == "a secret" + assert url.userinfo == b"jo%40email.com:a%20secret" + assert url.host == "müller.de" + assert url.raw_host == b"xn--mller-kva.de" + assert url.port == 1234 + assert url.netloc == b"xn--mller-kva.de:1234" + assert url.path == "/pa th" + assert url.query == b"?search=ab" + assert url.raw_path == b"/pa%20th?search=ab" + assert url.fragment == "anchorlink" + + The components of a URL are broken down like this: + + https://jo%40email.com:a%20secret@müller.de:1234/pa%20th?search=ab#anchorlink + [scheme] [ username ] [password] [ host ][port][ path ] [ query ] [fragment] + [ userinfo ] [ netloc ][ raw_path ] + + Note that: + + * `url.scheme` is normalized to always be lowercased. + + * `url.host` is normalized to always be lowercased. Internationalized domain + names are represented in unicode, without IDNA encoding applied. For instance: + + url = httpx.URL("http://中国.icom.museum") + assert url.host == "中国.icom.museum" + url = httpx.URL("http://xn--fiqs8s.icom.museum") + assert url.host == "中国.icom.museum" + + * `url.raw_host` is normalized to always be lowercased, and is IDNA encoded. + + url = httpx.URL("http://中国.icom.museum") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + url = httpx.URL("http://xn--fiqs8s.icom.museum") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + + * `url.port` is either None or an integer. URLs that include the default port for + "http", "https", "ws", "wss", and "ftp" schemes have their port + normalized to `None`. + + assert httpx.URL("http://example.com") == httpx.URL("http://example.com:80") + assert httpx.URL("http://example.com").port is None + assert httpx.URL("http://example.com:80").port is None + + * `url.userinfo` is raw bytes, without URL escaping. Usually you'll want to work + with `url.username` and `url.password` instead, which handle the URL escaping. + + * `url.raw_path` is raw bytes of both the path and query, without URL escaping. + This portion is used as the target when constructing HTTP requests. Usually you'll + want to work with `url.path` instead. + + * `url.query` is raw bytes, without URL escaping. A URL query string portion can + only be properly URL escaped when decoding the parameter names and values + themselves. + """ + + def __init__(self, url: URL | str = "", **kwargs: typing.Any) -> None: + if kwargs: + allowed = { + "scheme": str, + "username": str, + "password": str, + "userinfo": bytes, + "host": str, + "port": int, + "netloc": bytes, + "path": str, + "query": bytes, + "raw_path": bytes, + "fragment": str, + "params": object, + } + + # Perform type checking for all supported keyword arguments. + for key, value in kwargs.items(): + if key not in allowed: + message = f"{key!r} is an invalid keyword argument for URL()" + raise TypeError(message) + if value is not None and not isinstance(value, allowed[key]): + expected = allowed[key].__name__ + seen = type(value).__name__ + message = f"Argument {key!r} must be {expected} but got {seen}" + raise TypeError(message) + if isinstance(value, bytes): + kwargs[key] = value.decode("ascii") + + if "params" in kwargs: + # Replace any "params" keyword with the raw "query" instead. + # + # Ensure that empty params use `kwargs["query"] = None` rather + # than `kwargs["query"] = ""`, so that generated URLs do not + # include an empty trailing "?". + params = kwargs.pop("params") + kwargs["query"] = None if not params else str(QueryParams(params)) + + if isinstance(url, str): + self._uri_reference = urlparse(url, **kwargs) + elif isinstance(url, URL): + self._uri_reference = url._uri_reference.copy_with(**kwargs) + else: + raise TypeError( + "Invalid type for url. Expected str or httpx.URL," + f" got {type(url)}: {url!r}" + ) + + @property + def scheme(self) -> str: + """ + The URL scheme, such as "http", "https". + Always normalised to lowercase. + """ + return self._uri_reference.scheme + + @property + def raw_scheme(self) -> bytes: + """ + The raw bytes representation of the URL scheme, such as b"http", b"https". + Always normalised to lowercase. + """ + return self._uri_reference.scheme.encode("ascii") + + @property + def userinfo(self) -> bytes: + """ + The URL userinfo as a raw bytestring. + For example: b"jo%40email.com:a%20secret". + """ + return self._uri_reference.userinfo.encode("ascii") + + @property + def username(self) -> str: + """ + The URL username as a string, with URL decoding applied. + For example: "jo@email.com" + """ + userinfo = self._uri_reference.userinfo + return unquote(userinfo.partition(":")[0]) + + @property + def password(self) -> str: + """ + The URL password as a string, with URL decoding applied. + For example: "a secret" + """ + userinfo = self._uri_reference.userinfo + return unquote(userinfo.partition(":")[2]) + + @property + def host(self) -> str: + """ + The URL host as a string. + Always normalized to lowercase, with IDNA hosts decoded into unicode. + + Examples: + + url = httpx.URL("http://www.EXAMPLE.org") + assert url.host == "www.example.org" + + url = httpx.URL("http://中国.icom.museum") + assert url.host == "中国.icom.museum" + + url = httpx.URL("http://xn--fiqs8s.icom.museum") + assert url.host == "中国.icom.museum" + + url = httpx.URL("https://[::ffff:192.168.0.1]") + assert url.host == "::ffff:192.168.0.1" + """ + host: str = self._uri_reference.host + + if host.startswith("xn--"): + host = idna.decode(host) + + return host + + @property + def raw_host(self) -> bytes: + """ + The raw bytes representation of the URL host. + Always normalized to lowercase, and IDNA encoded. + + Examples: + + url = httpx.URL("http://www.EXAMPLE.org") + assert url.raw_host == b"www.example.org" + + url = httpx.URL("http://中国.icom.museum") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + + url = httpx.URL("http://xn--fiqs8s.icom.museum") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + + url = httpx.URL("https://[::ffff:192.168.0.1]") + assert url.raw_host == b"::ffff:192.168.0.1" + """ + return self._uri_reference.host.encode("ascii") + + @property + def port(self) -> int | None: + """ + The URL port as an integer. + + Note that the URL class performs port normalization as per the WHATWG spec. + Default ports for "http", "https", "ws", "wss", and "ftp" schemes are always + treated as `None`. + + For example: + + assert httpx.URL("http://www.example.com") == httpx.URL("http://www.example.com:80") + assert httpx.URL("http://www.example.com:80").port is None + """ + return self._uri_reference.port + + @property + def netloc(self) -> bytes: + """ + Either `` or `:` as bytes. + Always normalized to lowercase, and IDNA encoded. + + This property may be used for generating the value of a request + "Host" header. + """ + return self._uri_reference.netloc.encode("ascii") + + @property + def path(self) -> str: + """ + The URL path as a string. Excluding the query string, and URL decoded. + + For example: + + url = httpx.URL("https://example.com/pa%20th") + assert url.path == "/pa th" + """ + path = self._uri_reference.path or "/" + return unquote(path) + + @property + def query(self) -> bytes: + """ + The URL query string, as raw bytes, excluding the leading b"?". + + This is necessarily a bytewise interface, because we cannot + perform URL decoding of this representation until we've parsed + the keys and values into a QueryParams instance. + + For example: + + url = httpx.URL("https://example.com/?filter=some%20search%20terms") + assert url.query == b"filter=some%20search%20terms" + """ + query = self._uri_reference.query or "" + return query.encode("ascii") + + @property + def params(self) -> QueryParams: + """ + The URL query parameters, neatly parsed and packaged into an immutable + multidict representation. + """ + return QueryParams(self._uri_reference.query) + + @property + def raw_path(self) -> bytes: + """ + The complete URL path and query string as raw bytes. + Used as the target when constructing HTTP requests. + + For example: + + GET /users?search=some%20text HTTP/1.1 + Host: www.example.org + Connection: close + """ + path = self._uri_reference.path or "/" + if self._uri_reference.query is not None: + path += "?" + self._uri_reference.query + return path.encode("ascii") + + @property + def fragment(self) -> str: + """ + The URL fragments, as used in HTML anchors. + As a string, without the leading '#'. + """ + return unquote(self._uri_reference.fragment or "") + + @property + def is_absolute_url(self) -> bool: + """ + Return `True` for absolute URLs such as 'http://example.com/path', + and `False` for relative URLs such as '/path'. + """ + # We don't use `.is_absolute` from `rfc3986` because it treats + # URLs with a fragment portion as not absolute. + # What we actually care about is if the URL provides + # a scheme and hostname to which connections should be made. + return bool(self._uri_reference.scheme and self._uri_reference.host) + + @property + def is_relative_url(self) -> bool: + """ + Return `False` for absolute URLs such as 'http://example.com/path', + and `True` for relative URLs such as '/path'. + """ + return not self.is_absolute_url + + def copy_with(self, **kwargs: typing.Any) -> URL: + """ + Copy this URL, returning a new URL with some components altered. + Accepts the same set of parameters as the components that are made + available via properties on the `URL` class. + + For example: + + url = httpx.URL("https://www.example.com").copy_with( + username="jo@gmail.com", password="a secret" + ) + assert url == "https://jo%40email.com:a%20secret@www.example.com" + """ + return URL(self, **kwargs) + + def copy_set_param(self, key: str, value: typing.Any = None) -> URL: + return self.copy_with(params=self.params.set(key, value)) + + def copy_add_param(self, key: str, value: typing.Any = None) -> URL: + return self.copy_with(params=self.params.add(key, value)) + + def copy_remove_param(self, key: str) -> URL: + return self.copy_with(params=self.params.remove(key)) + + def copy_merge_params(self, params: QueryParamTypes) -> URL: + return self.copy_with(params=self.params.merge(params)) + + def join(self, url: URL | str) -> URL: + """ + Return an absolute URL, using this URL as the base. + + Eg. + + url = httpx.URL("https://www.example.com/test") + url = url.join("/new/path") + assert url == "https://www.example.com/new/path" + """ + from urllib.parse import urljoin + + return URL(urljoin(str(self), str(URL(url)))) + + def __hash__(self) -> int: + return hash(str(self)) + + def __eq__(self, other: typing.Any) -> bool: + return isinstance(other, (URL, str)) and str(self) == str(URL(other)) + + def __str__(self) -> str: + return str(self._uri_reference) + + def __repr__(self) -> str: + scheme, userinfo, host, port, path, query, fragment = self._uri_reference + + if ":" in userinfo: + # Mask any password component. + userinfo = f'{userinfo.split(":")[0]}:[secure]' + + authority = "".join( + [ + f"{userinfo}@" if userinfo else "", + f"[{host}]" if ":" in host else host, + f":{port}" if port is not None else "", + ] + ) + url = "".join( + [ + f"{self.scheme}:" if scheme else "", + f"//{authority}" if authority else "", + path, + f"?{query}" if query is not None else "", + f"#{fragment}" if fragment is not None else "", + ] + ) + + return f"{self.__class__.__name__}({url!r})" + + @property + def raw(self) -> tuple[bytes, bytes, int, bytes]: # pragma: nocover + import collections + import warnings + + warnings.warn("URL.raw is deprecated.") + RawURL = collections.namedtuple( + "RawURL", ["raw_scheme", "raw_host", "port", "raw_path"] + ) + return RawURL( + raw_scheme=self.raw_scheme, + raw_host=self.raw_host, + port=self.port, + raw_path=self.raw_path, + ) + + +class QueryParams(typing.Mapping[str, str]): + """ + URL query parameters, as a multi-dict. + """ + + def __init__(self, *args: QueryParamTypes | None, **kwargs: typing.Any) -> None: + assert len(args) < 2, "Too many arguments." + assert not (args and kwargs), "Cannot mix named and unnamed arguments." + + value = args[0] if args else kwargs + + if value is None or isinstance(value, (str, bytes)): + value = value.decode("ascii") if isinstance(value, bytes) else value + self._dict = parse_qs(value, keep_blank_values=True) + elif isinstance(value, QueryParams): + self._dict = {k: list(v) for k, v in value._dict.items()} + else: + dict_value: dict[typing.Any, list[typing.Any]] = {} + if isinstance(value, (list, tuple)): + # Convert list inputs like: + # [("a", "123"), ("a", "456"), ("b", "789")] + # To a dict representation, like: + # {"a": ["123", "456"], "b": ["789"]} + for item in value: + dict_value.setdefault(item[0], []).append(item[1]) + else: + # Convert dict inputs like: + # {"a": "123", "b": ["456", "789"]} + # To dict inputs where values are always lists, like: + # {"a": ["123"], "b": ["456", "789"]} + dict_value = { + k: list(v) if isinstance(v, (list, tuple)) else [v] + for k, v in value.items() + } + + # Ensure that keys and values are neatly coerced to strings. + # We coerce values `True` and `False` to JSON-like "true" and "false" + # representations, and coerce `None` values to the empty string. + self._dict = { + str(k): [primitive_value_to_str(item) for item in v] + for k, v in dict_value.items() + } + + def keys(self) -> typing.KeysView[str]: + """ + Return all the keys in the query params. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert list(q.keys()) == ["a", "b"] + """ + return self._dict.keys() + + def values(self) -> typing.ValuesView[str]: + """ + Return all the values in the query params. If a key occurs more than once + only the first item for that key is returned. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert list(q.values()) == ["123", "789"] + """ + return {k: v[0] for k, v in self._dict.items()}.values() + + def items(self) -> typing.ItemsView[str, str]: + """ + Return all items in the query params. If a key occurs more than once + only the first item for that key is returned. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert list(q.items()) == [("a", "123"), ("b", "789")] + """ + return {k: v[0] for k, v in self._dict.items()}.items() + + def multi_items(self) -> list[tuple[str, str]]: + """ + Return all items in the query params. Allow duplicate keys to occur. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert list(q.multi_items()) == [("a", "123"), ("a", "456"), ("b", "789")] + """ + multi_items: list[tuple[str, str]] = [] + for k, v in self._dict.items(): + multi_items.extend([(k, i) for i in v]) + return multi_items + + def get(self, key: typing.Any, default: typing.Any = None) -> typing.Any: + """ + Get a value from the query param for a given key. If the key occurs + more than once, then only the first value is returned. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert q.get("a") == "123" + """ + if key in self._dict: + return self._dict[str(key)][0] + return default + + def get_list(self, key: str) -> list[str]: + """ + Get all values from the query param for a given key. + + Usage: + + q = httpx.QueryParams("a=123&a=456&b=789") + assert q.get_list("a") == ["123", "456"] + """ + return list(self._dict.get(str(key), [])) + + def set(self, key: str, value: typing.Any = None) -> QueryParams: + """ + Return a new QueryParams instance, setting the value of a key. + + Usage: + + q = httpx.QueryParams("a=123") + q = q.set("a", "456") + assert q == httpx.QueryParams("a=456") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict[str(key)] = [primitive_value_to_str(value)] + return q + + def add(self, key: str, value: typing.Any = None) -> QueryParams: + """ + Return a new QueryParams instance, setting or appending the value of a key. + + Usage: + + q = httpx.QueryParams("a=123") + q = q.add("a", "456") + assert q == httpx.QueryParams("a=123&a=456") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict[str(key)] = q.get_list(key) + [primitive_value_to_str(value)] + return q + + def remove(self, key: str) -> QueryParams: + """ + Return a new QueryParams instance, removing the value of a key. + + Usage: + + q = httpx.QueryParams("a=123") + q = q.remove("a") + assert q == httpx.QueryParams("") + """ + q = QueryParams() + q._dict = dict(self._dict) + q._dict.pop(str(key), None) + return q + + def merge(self, params: QueryParamTypes | None = None) -> QueryParams: + """ + Return a new QueryParams instance, updated with. + + Usage: + + q = httpx.QueryParams("a=123") + q = q.merge({"b": "456"}) + assert q == httpx.QueryParams("a=123&b=456") + + q = httpx.QueryParams("a=123") + q = q.merge({"a": "456", "b": "789"}) + assert q == httpx.QueryParams("a=456&b=789") + """ + q = QueryParams(params) + q._dict = {**self._dict, **q._dict} + return q + + def __getitem__(self, key: typing.Any) -> str: + return self._dict[key][0] + + def __contains__(self, key: typing.Any) -> bool: + return key in self._dict + + def __iter__(self) -> typing.Iterator[typing.Any]: + return iter(self.keys()) + + def __len__(self) -> int: + return len(self._dict) + + def __bool__(self) -> bool: + return bool(self._dict) + + def __hash__(self) -> int: + return hash(str(self)) + + def __eq__(self, other: typing.Any) -> bool: + if not isinstance(other, self.__class__): + return False + return sorted(self.multi_items()) == sorted(other.multi_items()) + + def __str__(self) -> str: + return urlencode(self.multi_items()) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + query_string = str(self) + return f"{class_name}({query_string!r})" + + def update(self, params: QueryParamTypes | None = None) -> None: + raise RuntimeError( + "QueryParams are immutable since 0.18.0. " + "Use `q = q.merge(...)` to create an updated copy." + ) + + def __setitem__(self, key: str, value: str) -> None: + raise RuntimeError( + "QueryParams are immutable since 0.18.0. " + "Use `q = q.set(key, value)` to create an updated copy." + ) diff --git a/.venv/Lib/site-packages/httpx/_utils.py b/.venv/Lib/site-packages/httpx/_utils.py new file mode 100644 index 0000000..7fe827d --- /dev/null +++ b/.venv/Lib/site-packages/httpx/_utils.py @@ -0,0 +1,242 @@ +from __future__ import annotations + +import ipaddress +import os +import re +import typing +from urllib.request import getproxies + +from ._types import PrimitiveData + +if typing.TYPE_CHECKING: # pragma: no cover + from ._urls import URL + + +def primitive_value_to_str(value: PrimitiveData) -> str: + """ + Coerce a primitive data type into a string value. + + Note that we prefer JSON-style 'true'/'false' for boolean values here. + """ + if value is True: + return "true" + elif value is False: + return "false" + elif value is None: + return "" + return str(value) + + +def get_environment_proxies() -> dict[str, str | None]: + """Gets proxy information from the environment""" + + # urllib.request.getproxies() falls back on System + # Registry and Config for proxies on Windows and macOS. + # We don't want to propagate non-HTTP proxies into + # our configuration such as 'TRAVIS_APT_PROXY'. + proxy_info = getproxies() + mounts: dict[str, str | None] = {} + + for scheme in ("http", "https", "all"): + if proxy_info.get(scheme): + hostname = proxy_info[scheme] + mounts[f"{scheme}://"] = ( + hostname if "://" in hostname else f"http://{hostname}" + ) + + no_proxy_hosts = [host.strip() for host in proxy_info.get("no", "").split(",")] + for hostname in no_proxy_hosts: + # See https://curl.haxx.se/libcurl/c/CURLOPT_NOPROXY.html for details + # on how names in `NO_PROXY` are handled. + if hostname == "*": + # If NO_PROXY=* is used or if "*" occurs as any one of the comma + # separated hostnames, then we should just bypass any information + # from HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and always ignore + # proxies. + return {} + elif hostname: + # NO_PROXY=.google.com is marked as "all://*.google.com, + # which disables "www.google.com" but not "google.com" + # NO_PROXY=google.com is marked as "all://*google.com, + # which disables "www.google.com" and "google.com". + # (But not "wwwgoogle.com") + # NO_PROXY can include domains, IPv6, IPv4 addresses and "localhost" + # NO_PROXY=example.com,::1,localhost,192.168.0.0/16 + if "://" in hostname: + mounts[hostname] = None + elif is_ipv4_hostname(hostname): + mounts[f"all://{hostname}"] = None + elif is_ipv6_hostname(hostname): + mounts[f"all://[{hostname}]"] = None + elif hostname.lower() == "localhost": + mounts[f"all://{hostname}"] = None + else: + mounts[f"all://*{hostname}"] = None + + return mounts + + +def to_bytes(value: str | bytes, encoding: str = "utf-8") -> bytes: + return value.encode(encoding) if isinstance(value, str) else value + + +def to_str(value: str | bytes, encoding: str = "utf-8") -> str: + return value if isinstance(value, str) else value.decode(encoding) + + +def to_bytes_or_str(value: str, match_type_of: typing.AnyStr) -> typing.AnyStr: + return value if isinstance(match_type_of, str) else value.encode() + + +def unquote(value: str) -> str: + return value[1:-1] if value[0] == value[-1] == '"' else value + + +def peek_filelike_length(stream: typing.Any) -> int | None: + """ + Given a file-like stream object, return its length in number of bytes + without reading it into memory. + """ + try: + # Is it an actual file? + fd = stream.fileno() + # Yup, seems to be an actual file. + length = os.fstat(fd).st_size + except (AttributeError, OSError): + # No... Maybe it's something that supports random access, like `io.BytesIO`? + try: + # Assuming so, go to end of stream to figure out its length, + # then put it back in place. + offset = stream.tell() + length = stream.seek(0, os.SEEK_END) + stream.seek(offset) + except (AttributeError, OSError): + # Not even that? Sorry, we're doomed... + return None + + return length + + +class URLPattern: + """ + A utility class currently used for making lookups against proxy keys... + + # Wildcard matching... + >>> pattern = URLPattern("all://") + >>> pattern.matches(httpx.URL("http://example.com")) + True + + # Witch scheme matching... + >>> pattern = URLPattern("https://") + >>> pattern.matches(httpx.URL("https://example.com")) + True + >>> pattern.matches(httpx.URL("http://example.com")) + False + + # With domain matching... + >>> pattern = URLPattern("https://example.com") + >>> pattern.matches(httpx.URL("https://example.com")) + True + >>> pattern.matches(httpx.URL("http://example.com")) + False + >>> pattern.matches(httpx.URL("https://other.com")) + False + + # Wildcard scheme, with domain matching... + >>> pattern = URLPattern("all://example.com") + >>> pattern.matches(httpx.URL("https://example.com")) + True + >>> pattern.matches(httpx.URL("http://example.com")) + True + >>> pattern.matches(httpx.URL("https://other.com")) + False + + # With port matching... + >>> pattern = URLPattern("https://example.com:1234") + >>> pattern.matches(httpx.URL("https://example.com:1234")) + True + >>> pattern.matches(httpx.URL("https://example.com")) + False + """ + + def __init__(self, pattern: str) -> None: + from ._urls import URL + + if pattern and ":" not in pattern: + raise ValueError( + f"Proxy keys should use proper URL forms rather " + f"than plain scheme strings. " + f'Instead of "{pattern}", use "{pattern}://"' + ) + + url = URL(pattern) + self.pattern = pattern + self.scheme = "" if url.scheme == "all" else url.scheme + self.host = "" if url.host == "*" else url.host + self.port = url.port + if not url.host or url.host == "*": + self.host_regex: typing.Pattern[str] | None = None + elif url.host.startswith("*."): + # *.example.com should match "www.example.com", but not "example.com" + domain = re.escape(url.host[2:]) + self.host_regex = re.compile(f"^.+\\.{domain}$") + elif url.host.startswith("*"): + # *example.com should match "www.example.com" and "example.com" + domain = re.escape(url.host[1:]) + self.host_regex = re.compile(f"^(.+\\.)?{domain}$") + else: + # example.com should match "example.com" but not "www.example.com" + domain = re.escape(url.host) + self.host_regex = re.compile(f"^{domain}$") + + def matches(self, other: URL) -> bool: + if self.scheme and self.scheme != other.scheme: + return False + if ( + self.host + and self.host_regex is not None + and not self.host_regex.match(other.host) + ): + return False + if self.port is not None and self.port != other.port: + return False + return True + + @property + def priority(self) -> tuple[int, int, int]: + """ + The priority allows URLPattern instances to be sortable, so that + we can match from most specific to least specific. + """ + # URLs with a port should take priority over URLs without a port. + port_priority = 0 if self.port is not None else 1 + # Longer hostnames should match first. + host_priority = -len(self.host) + # Longer schemes should match first. + scheme_priority = -len(self.scheme) + return (port_priority, host_priority, scheme_priority) + + def __hash__(self) -> int: + return hash(self.pattern) + + def __lt__(self, other: URLPattern) -> bool: + return self.priority < other.priority + + def __eq__(self, other: typing.Any) -> bool: + return isinstance(other, URLPattern) and self.pattern == other.pattern + + +def is_ipv4_hostname(hostname: str) -> bool: + try: + ipaddress.IPv4Address(hostname.split("/")[0]) + except Exception: + return False + return True + + +def is_ipv6_hostname(hostname: str) -> bool: + try: + ipaddress.IPv6Address(hostname.split("/")[0]) + except Exception: + return False + return True diff --git a/.venv/Lib/site-packages/httpx/py.typed b/.venv/Lib/site-packages/httpx/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/idna-3.10.dist-info/INSTALLER b/.venv/Lib/site-packages/idna-3.10.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/idna-3.10.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/idna-3.10.dist-info/LICENSE.md b/.venv/Lib/site-packages/idna-3.10.dist-info/LICENSE.md new file mode 100644 index 0000000..19b6b45 --- /dev/null +++ b/.venv/Lib/site-packages/idna-3.10.dist-info/LICENSE.md @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2013-2024, Kim Davies and contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/Lib/site-packages/idna-3.10.dist-info/METADATA b/.venv/Lib/site-packages/idna-3.10.dist-info/METADATA new file mode 100644 index 0000000..c42623e --- /dev/null +++ b/.venv/Lib/site-packages/idna-3.10.dist-info/METADATA @@ -0,0 +1,250 @@ +Metadata-Version: 2.1 +Name: idna +Version: 3.10 +Summary: Internationalized Domain Names in Applications (IDNA) +Author-email: Kim Davies +Requires-Python: >=3.6 +Description-Content-Type: text/x-rst +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: Name Service (DNS) +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Utilities +Requires-Dist: ruff >= 0.6.2 ; extra == "all" +Requires-Dist: mypy >= 1.11.2 ; extra == "all" +Requires-Dist: pytest >= 8.3.2 ; extra == "all" +Requires-Dist: flake8 >= 7.1.1 ; extra == "all" +Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.rst +Project-URL: Issue tracker, https://github.com/kjd/idna/issues +Project-URL: Source, https://github.com/kjd/idna +Provides-Extra: all + +Internationalized Domain Names in Applications (IDNA) +===================================================== + +Support for the Internationalized Domain Names in +Applications (IDNA) protocol as specified in `RFC 5891 +`_. This is the latest version of +the protocol and is sometimes referred to as “IDNA 2008”. + +This library also provides support for Unicode Technical +Standard 46, `Unicode IDNA Compatibility Processing +`_. + +This acts as a suitable replacement for the “encodings.idna” +module that comes with the Python standard library, but which +only supports the older superseded IDNA specification (`RFC 3490 +`_). + +Basic functions are simply executed: + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('ドメイン.テスト') + b'xn--eckwd4c7c.xn--zckzah' + >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) + ドメイン.テスト + + +Installation +------------ + +This package is available for installation from PyPI: + +.. code-block:: bash + + $ python3 -m pip install idna + + +Usage +----- + +For typical usage, the ``encode`` and ``decode`` functions will take a +domain name argument and perform a conversion to A-labels or U-labels +respectively. + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('ドメイン.テスト') + b'xn--eckwd4c7c.xn--zckzah' + >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) + ドメイン.テスト + +You may use the codec encoding and decoding methods using the +``idna.codec`` module: + +.. code-block:: pycon + + >>> import idna.codec + >>> print('домен.испытание'.encode('idna2008')) + b'xn--d1acufc.xn--80akhbyknj4f' + >>> print(b'xn--d1acufc.xn--80akhbyknj4f'.decode('idna2008')) + домен.испытание + +Conversions can be applied at a per-label basis using the ``ulabel`` or +``alabel`` functions if necessary: + +.. code-block:: pycon + + >>> idna.alabel('测试') + b'xn--0zwm56d' + +Compatibility Mapping (UTS #46) ++++++++++++++++++++++++++++++++ + +As described in `RFC 5895 `_, the +IDNA specification does not normalize input from different potential +ways a user may input a domain name. This functionality, known as +a “mapping”, is considered by the specification to be a local +user-interface issue distinct from IDNA conversion functionality. + +This library provides one such mapping that was developed by the +Unicode Consortium. Known as `Unicode IDNA Compatibility Processing +`_, it provides for both a regular +mapping for typical applications, as well as a transitional mapping to +help migrate from older IDNA 2003 applications. Strings are +preprocessed according to Section 4.4 “Preprocessing for IDNA2008” +prior to the IDNA operations. + +For example, “Königsgäßchen” is not a permissible label as *LATIN +CAPITAL LETTER K* is not allowed (nor are capital letters in general). +UTS 46 will convert this into lower case prior to applying the IDNA +conversion. + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('Königsgäßchen') + ... + idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed + >>> idna.encode('Königsgäßchen', uts46=True) + b'xn--knigsgchen-b4a3dun' + >>> print(idna.decode('xn--knigsgchen-b4a3dun')) + königsgäßchen + +Transitional processing provides conversions to help transition from +the older 2003 standard to the current standard. For example, in the +original IDNA specification, the *LATIN SMALL LETTER SHARP S* (ß) was +converted into two *LATIN SMALL LETTER S* (ss), whereas in the current +IDNA specification this conversion is not performed. + +.. code-block:: pycon + + >>> idna.encode('Königsgäßchen', uts46=True, transitional=True) + 'xn--knigsgsschen-lcb0w' + +Implementers should use transitional processing with caution, only in +rare cases where conversion from legacy labels to current labels must be +performed (i.e. IDNA implementations that pre-date 2008). For typical +applications that just need to convert labels, transitional processing +is unlikely to be beneficial and could produce unexpected incompatible +results. + +``encodings.idna`` Compatibility +++++++++++++++++++++++++++++++++ + +Function calls from the Python built-in ``encodings.idna`` module are +mapped to their IDNA 2008 equivalents using the ``idna.compat`` module. +Simply substitute the ``import`` clause in your code to refer to the new +module name. + +Exceptions +---------- + +All errors raised during the conversion following the specification +should raise an exception derived from the ``idna.IDNAError`` base +class. + +More specific exceptions that may be generated as ``idna.IDNABidiError`` +when the error reflects an illegal combination of left-to-right and +right-to-left characters in a label; ``idna.InvalidCodepoint`` when +a specific codepoint is an illegal character in an IDN label (i.e. +INVALID); and ``idna.InvalidCodepointContext`` when the codepoint is +illegal based on its positional context (i.e. it is CONTEXTO or CONTEXTJ +but the contextual requirements are not satisfied.) + +Building and Diagnostics +------------------------ + +The IDNA and UTS 46 functionality relies upon pre-calculated lookup +tables for performance. These tables are derived from computing against +eligibility criteria in the respective standards. These tables are +computed using the command-line script ``tools/idna-data``. + +This tool will fetch relevant codepoint data from the Unicode repository +and perform the required calculations to identify eligibility. There are +three main modes: + +* ``idna-data make-libdata``. Generates ``idnadata.py`` and + ``uts46data.py``, the pre-calculated lookup tables used for IDNA and + UTS 46 conversions. Implementers who wish to track this library against + a different Unicode version may use this tool to manually generate a + different version of the ``idnadata.py`` and ``uts46data.py`` files. + +* ``idna-data make-table``. Generate a table of the IDNA disposition + (e.g. PVALID, CONTEXTJ, CONTEXTO) in the format found in Appendix + B.1 of RFC 5892 and the pre-computed tables published by `IANA + `_. + +* ``idna-data U+0061``. Prints debugging output on the various + properties associated with an individual Unicode codepoint (in this + case, U+0061), that are used to assess the IDNA and UTS 46 status of a + codepoint. This is helpful in debugging or analysis. + +The tool accepts a number of arguments, described using ``idna-data +-h``. Most notably, the ``--version`` argument allows the specification +of the version of Unicode to be used in computing the table data. For +example, ``idna-data --version 9.0.0 make-libdata`` will generate +library data against Unicode 9.0.0. + + +Additional Notes +---------------- + +* **Packages**. The latest tagged release version is published in the + `Python Package Index `_. + +* **Version support**. This library supports Python 3.6 and higher. + As this library serves as a low-level toolkit for a variety of + applications, many of which strive for broad compatibility with older + Python versions, there is no rush to remove older interpreter support. + Removing support for older versions should be well justified in that the + maintenance burden has become too high. + +* **Python 2**. Python 2 is supported by version 2.x of this library. + Use "idna<3" in your requirements file if you need this library for + a Python 2 application. Be advised that these versions are no longer + actively developed. + +* **Testing**. The library has a test suite based on each rule of the + IDNA specification, as well as tests that are provided as part of the + Unicode Technical Standard 46, `Unicode IDNA Compatibility Processing + `_. + +* **Emoji**. It is an occasional request to support emoji domains in + this library. Encoding of symbols like emoji is expressly prohibited by + the technical standard IDNA 2008 and emoji domains are broadly phased + out across the domain industry due to associated security risks. For + now, applications that need to support these non-compliant labels + may wish to consider trying the encode/decode operation in this library + first, and then falling back to using `encodings.idna`. See `the Github + project `_ for more discussion. + diff --git a/.venv/Lib/site-packages/idna-3.10.dist-info/RECORD b/.venv/Lib/site-packages/idna-3.10.dist-info/RECORD new file mode 100644 index 0000000..54f6609 --- /dev/null +++ b/.venv/Lib/site-packages/idna-3.10.dist-info/RECORD @@ -0,0 +1,22 @@ +idna-3.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +idna-3.10.dist-info/LICENSE.md,sha256=pZ8LDvNjWHQQmkRhykT_enDVBpboFHZ7-vch1Mmw2w8,1541 +idna-3.10.dist-info/METADATA,sha256=URR5ZyDfQ1PCEGhkYoojqfi2Ra0tau2--lhwG4XSfjI,10158 +idna-3.10.dist-info/RECORD,, +idna-3.10.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868 +idna/__pycache__/__init__.cpython-312.pyc,, +idna/__pycache__/codec.cpython-312.pyc,, +idna/__pycache__/compat.cpython-312.pyc,, +idna/__pycache__/core.cpython-312.pyc,, +idna/__pycache__/idnadata.cpython-312.pyc,, +idna/__pycache__/intranges.cpython-312.pyc,, +idna/__pycache__/package_data.cpython-312.pyc,, +idna/__pycache__/uts46data.cpython-312.pyc,, +idna/codec.py,sha256=PEew3ItwzjW4hymbasnty2N2OXvNcgHB-JjrBuxHPYY,3422 +idna/compat.py,sha256=RzLy6QQCdl9784aFhb2EX9EKGCJjg0P3PilGdeXXcx8,316 +idna/core.py,sha256=YJYyAMnwiQEPjVC4-Fqu_p4CJ6yKKuDGmppBNQNQpFs,13239 +idna/idnadata.py,sha256=W30GcIGvtOWYwAjZj4ZjuouUutC6ffgNuyjJy7fZ-lo,78306 +idna/intranges.py,sha256=amUtkdhYcQG8Zr-CoMM_kVRacxkivC1WgxN1b63KKdU,1898 +idna/package_data.py,sha256=q59S3OXsc5VI8j6vSD0sGBMyk6zZ4vWFREE88yCJYKs,21 +idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +idna/uts46data.py,sha256=rt90K9J40gUSwppDPCrhjgi5AA6pWM65dEGRSf6rIhM,239289 diff --git a/.venv/Lib/site-packages/idna-3.10.dist-info/WHEEL b/.venv/Lib/site-packages/idna-3.10.dist-info/WHEEL new file mode 100644 index 0000000..3b5e64b --- /dev/null +++ b/.venv/Lib/site-packages/idna-3.10.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/Lib/site-packages/idna/__init__.py b/.venv/Lib/site-packages/idna/__init__.py new file mode 100644 index 0000000..cfdc030 --- /dev/null +++ b/.venv/Lib/site-packages/idna/__init__.py @@ -0,0 +1,45 @@ +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .intranges import intranges_contain +from .package_data import __version__ + +__all__ = [ + "__version__", + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/.venv/Lib/site-packages/idna/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/idna/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75cd625c2695340f9fae8f501b52a2c8f3967e09 GIT binary patch literal 899 zcmbu7O^XvT7{`-oJAIkHx7+m~(u-gZyI#b@BEq`sVnLRL#Y-3n=}c_XPLh(O#Xb5d zyn7J*EPjK4Cr{qQMHW1n%v296crl0HJkNiU|MTSKQ>)bgZhT0N7HJ89FFyG_f?aTJ zT<{L0AcYy=5J4WGfR|8-mr`okq4x{20Ux4a9+$E`K0+f1<{&Mpm^Q?o@0^#?lj+(zRs5*!|nei4=yuF*YO_nbFKPk_Amxc;@VEr-&C<>jf29uI#7~ zm|%vH3@4J$n4ro=gWO*x> zwqN|uSK^F{xmnmjp=qO-nD4|1v^}>)MZ}!y+>S8e#0cXpRhmhG@non*?$DIGO`ysi z6_3!P>QVEkd-$tYkw?>`<{@g+;?_(I7gO^lAu zh&IO;)A3f1&oZJ{@e5~5)%a8{Wqi1$VjI6?vsg1j4|WxG%u+!fV(gy<#)s=GTYJJ~ ty2}c`pjyR@Gg%;un+N_H+OM literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/idna/__pycache__/codec.cpython-312.pyc b/.venv/Lib/site-packages/idna/__pycache__/codec.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d3f55b512d62d99fbad594b197a5a9cf785a6ff GIT binary patch literal 4989 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 S7Jupeg}31S9Ht+);C}#{hZ@HK literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/idna/__pycache__/compat.cpython-312.pyc b/.venv/Lib/site-packages/idna/__pycache__/compat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7c2d846aac1c025bbd5d28c4909475451561091 GIT binary patch literal 903 zcmb7Cy=xRf6rb6z`%3%@N`geTu~>{TC;hP^#u;%RQ9tS`y0r;At4`gK$^*9uMXZAv+A#<8B~h1$T3# z!d;L-%(C!a#M3-f^LekRZ1HA$rPUIbFs*}gIDzIh`PR64boE1-)s9qjtyF$Ob(7(2@A}(O0gMR12lu8tKx2HgssuwjK zheCwCQjIdp=}3!_T-D{2vx5X4i&?xte-QZ$&A_CTekZd(i92v8ojH0iAm~mOhuRIQ jdTIM)@x9l-dFRXZxsv=ebwT>{$kb~-J+Xe3zU<)-)-B9P literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/idna/__pycache__/core.cpython-312.pyc b/.venv/Lib/site-packages/idna/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df73d5c39d00a06889653cd107d516839f14ccfe GIT binary patch literal 16134 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/idna/__pycache__/intranges.cpython-312.pyc b/.venv/Lib/site-packages/idna/__pycache__/intranges.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e64006e8e82e2f30d6bb45e71389bf74d4e4b9d4 GIT binary patch literal 2646 zcmZuyO>7%Q6rTNaTsuzUL`~Blnn?pF3&zm8P*hS7Buat)K%1sQ!Dzenj+1THyY9@o zt%CyraY&#NkwB`sxfhhG7jx^8BNCUSL13*aIKZVhqj2I<-ptxbQ!&!+yf^R7n>X)$ z@6FGNL=3_BK6iZfN(VxJa;7~ZEn@Q~5Vw$prjaFCvLj7PFe{E+P^OiFI;~2`Qi(#e zy`X1lL?xOP$;+I}t5(Ms>U6}4!rCtQG@|7rRt(lUthnGo8`j#|ogj%?36OL>ZU^@5 zf_>c}jg@7+$J`|$6gz2OBQ7?vV>2Ind2G8rnIV*=aKW?)E*1IW$gEc+`I6(5bx>sm z({Zp{DqJR%OC8Uh!L;PE7%(}{Wh7Vf!R%wUa$ccW@(FCFq(})Pu5bFb=ko3TEWvq? z7GfhK%r~ijIE9I89oADgp|gw4TNnX1TvEyAFr*tyfEg2~&rzn+)H;E%Pzk z6=ILtGq!6w;(~7+0-vyN7YKfD#PMcEhK1j{o{l}_i8`yxfqEJu5oi}mMTZ0$4~r2* zDDg|wP3ItDTO$gT0^ep2tlmQYmaKyAw&(?E1}(^zw4nH0Qw(2gQx&PL*Ph4GM;8%V zP%Ek>&x6|k>CVXr(cX$wQ8_sH9O??-$nyZjxe?HiZaxb#C6u*w_Mru>B6E;FIs9Gi zm5OF5!m?&bEt?>^3#anY_EbU@bwRoLfr|WiC;?yOwsaEds<|J6aK4>w$o!KC!;mfj z6M+P}Il_@ZO?QT{<1q}Q1qS0Kk$vN(EjSnLB|P8qa3MgS?>`vmR`8;_oVonV9TUdR3->}(ZqBoa#jn6@cgESq zdJD98bQEWf;0m_|F1nu2b$t#hmKN3?K7#R(1BLR~>X9JAJn9oG&_o;p#UXAGF^feo z5hM%<5*%rSGEG?0Bb0+Fh=&^u9$~<$a5IQGCP4Q(wS5vuc96)GD21{M4KUu9c$hK% z*B(2bonlb(*-5jI9Vcwg_lns`8>;_(>Rll@pJgY&)|>CYgRe0>0e0uIZ=1O}D8O;= zikD482f3C#ZC}nZIP8TYh)l?1*0x;pMaV$tP`X$S`nEVDPrZV0nD&D!o$#?5{1(wa zJ*aPQb@w+fES_p4x)&!J$-c#RA0&Fe&8&R9(Kq~S;^`%+5${~Oy8Lo$>3)2;(Y>#F zc=?ms{?&8!jwSm``5{+or*NNrC zw}sl7`q28*N9%B$eF`SVp%{7rXtT&R+UOuip5-dsIQJ*9(z|?e1$DkjIyJNG{<#JeiC?h#vMp(0ca5%HVI(rI5Uo7R`Y2 zZhT=v*90Dd$YmSeR6mG^v*D1uFOoue-RK<@!z98UL_}jS0;TA&K;??iPVAXkKadQ@ zBgIP|hCo}8+^$D>w|&!gsSY~N!N)#>-y-@ev9EfxmfT1TFHSs&_52n)upT>5y;!@r z5lbzOH6th*Upn{s^#}dKwTaaX)ZVe%?``xSU5+oQOQaF+UODWz4hfR!E(It;_|a)v>9S->UB zMd-SeV~TR91PZ?hM`u_-9I%0U+4CH69|3b=D!g`dwBT7KhrCYHpvZB;mf+V^BuV;P z)+BYa3rSsnpo7gwpR~8?*T%jpHxZDlQ}xU}rT*$2W0SMZlq3yS`QpVWs?=Wn4mJVXjCM=O>R1!Ow7Pqh)yGzE+!++?y$k>FRpIG>L>s39 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/idna/__pycache__/package_data.cpython-312.pyc b/.venv/Lib/site-packages/idna/__pycache__/package_data.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b336e26e058de5b043ae59b3ac7ed35fc0aa37aa GIT binary patch literal 230 zcmX@j%ge<81j~}GGDLy&V-N=h7@>^M96-i&h7^V&H#D}stBMMMQeM5Kgr=^Q$?kPs4j?=AFRLg<8GML-1X z8XGN5ed<%O`~3EQ=FHr^x8Gu|;CkfE{Qfg%=FFMdduC3#Z(nrL`Em5$iZ1nf>|Bu$ zcM=iz-??Gnr{-70#m$OKj7yB~9oHwmU3{NNyGWmeb_wxu=fx#P+LcI5=o;TOp=+dT z$*!fkMXsT2HcYVu#4NL+#1f-Qv^%F*IZ9HFbBmRu6y-RtSUJw29OoA+$GMc_f@0-3 zk8)gCtQ_Z4j*E(w;{wWYaj|k-NI5PkR*s7($EC%}aWUm6U923JP>wRi%5f>>xU5(? zN>h%@iK zj%z5#^~K6@E#pIqsw!6^fOk9ObyH zSUJj5j*7*~QGs&YU922;QI1N*%2APWR4!JIyD3MNV&$krIjR;bM`g-Utynp#P>$-w z%2Aba)F@VtYLug9v2s+W9JPv-qXy-uU9226DMy`R<)}qD>J}?UZOT!vSUKuYj(dug zqb}vRw^%vqQI7h>%5e|nxUX0_?xh?Jij|{2mE%du(WY2AT2YRtij|`^-sePJ7;aUX+Ys~U3dgBil3+A z=d1VyDt@7gU!>v}tN0}miHDt?oSmsRnbRs0qezg5L=Q}NqX{0cx4r@ zqT*FmyqbzvSMeGuUQ@+usd#M_ucP91RlJ^x-=pI9s(5`BzfZ**sCYvazhA{4Q1J&< zypf7Oq~eWL{9zS;M8%t^cvBT`rsB<2{81Heq2iCJcuN(3T*aSI@h4Thm5R4k@ir>{ zl!~|I@wM^RFt%$qyiNJ1iV!KEkXxd8bl{-elC1{!@7*=GWb3}setk!js#>#B)k;-z zO9fpA4G#KtvH9%Z2>o9I{rd^KwzJ~8)0XU#_$TAaM_QCmEFTFV=f+nrA1Oj<0XA?c z4C=zj?7}5jxFidgV&QXG7>9Ft&tqZK#qkSR_(B%Gh=ng^;Y(QfQWh@F!ev6z^(=e?3*X4XH?eS87QUH0E>WZ}ChoEzVUdP619TJ(p)m05}^j8|phYAjrxg=?^IO%}$X z-SKO)a2*z|%fj^#4twf7EPO8u*Jt7TShxWTH}v6zqWdYF8~ zJ{XI3p=hI6G?AhY#iCs)+Bg>NM$w03(e4y|Bo^&K(I&BIl%h>z(Vi4V4Z>>lqGq} zn^<%RMW2dAhf=g{EIN#$PsgIeDcUX;9YKBT8AQYD+Mb1Og@2-daeqV~tk=Hvz^=i-_D^=|)4oyHfxQOx>)-y#Xkyp0ErWhNyLK5g zuzllB0|(vTvPsz?1Isq<-DzO2_K$Sx(yMDwwo$*H{n}R=(zWl9_RXW6+YgKm>Uwv7 zk~($oI<2+`z8gpQ{p#qjF*Deu7;~iu*CrHU7d< zKg4y7FIg%WLkJc1|G^d~(7*rRfEOi_~` z!c$myDhp3z;pr?qgN0|Z@GKUd&BAk7crFXiW8wKMynuxlvhX4wPAJMi7*{SgzAQES zVnoA{yo809vhXq%Ue3ZRSa_umClswh7)1=ms49CXoyu9w^fd@K!=L8#heUJ%SBZ5j zyq?0j@l7bj22T@;HX@9}pSFh|MfxVMM~X6iIH71W!YC>?z8aasEElzXH=$??06W%5 zO0?A_It{~cIGYZ?4Jd4-?LM4Pv;$#wrYkA?PB2?!Rf_I%(Vz!~=vcb}%&`)RvYj}% zB!#M;7&)p2W!dvDin`|rTHU0L6zv5KkM@d7@iQv^dIn{BRied&UXy4Eq1Po^O6U!V zmJxbWqUD74Nwk8{TN14_e!%`Q!x7^!G}BdsNIZ2CWH6%;dKdcV|HEnE8vnnBIiYAj zNv5Uk`L3eU4@k6{&_RjT5PDmpwS*2yw2sg_60Ik6SfUMtj!3kT&{2st5qejmOhWHT zw3*QR5@iwkK%y;#K9p!Hp^qfmM&0aVps-_nBIO;F<5P)t63Ufm7opE2+D+(liLwcO zA<-T}UrLli=qp0G@r`mL0|!#|jv;}mUZm(a3!h-&lPr9Sh4WZApM?upxR8a5Somue z{)UCWW#Rv@@OLcyJq!Q9!vAIAA6fV(7XF!qPqXkZEc`1A|Hi_$azU@|#{WO}m5%O#|oN^(gimy&WC$>%Wn zTq%zs`8+0{FXeG0U%=!Gr96S;iY-^ApyQeI5*%}l;U%1cSU zmC3hBc{$0qGx-iD8;|+MN|Ntnaycj4zP6g=@=UHE<+UW=<>WJXk7_|1N@Wbq*?5m= zMOo~6D)w$yZ25#}B`I$txiXWhco_~VdI){ID$}buJvS1im1eYti`zEXO37=ok4%hKG*rgh(hO?Z^a zEu;*a@EDU@N*OlcaV9_EWLq|D!jnvH6@?#PcgZzlwlK|W^y|z z!zMh#w_BJQ&M%R^Dj3 zEDD2d2CK%uW+)d;lU&-Z>2-Fyj7HA70a>O$ul!AnbF$41 zL$RL88=P!&r&D~qJe%ZCnEa`f=aQVu zi~-b?a7Os0ol*t~52NRs1$+yYq2uqz2nE+J+3 z!IDfaC1orQ&SCPoQZ@@7@Bb~^k&E)7ko>3rfzgfky zEcJ!FZ!YDy&m()36;w(CmgRo0Qy}huS;aE>K_TzMY@?Aw=5iezL+Bxi#uIAnkhy9H zFuWfI3T?+DQpWIZ!sMn>#_(>&@1(QN`9qpNP@t~BkFl*#d2`cnZ`XMy; zLk^mYdl(M1u|EuT41}?*PrRk)3DN#8o2l+hg z3t%Uo!By1^UZD0jX8WvM)i9K?)?SYh2*>)BX_x0+9jMt}a(Zt3JrsQzQFH;*97k}z zNkHMzM!D1hZbDG{R0mGut{vUqG}mIZJBqy)bO$M#=8#$Jn~k+}hiodksT3U}5#3aZ zj+F?ucN|byqwzvMg5~f8CQp>|1Uld(i7=a=3>5aaDMCI>x2K|0C8FC?(P(^^yC4MSI$)cR?ombONd&_*-=P4vhX^ci zz-FU6RMCYJVM(+|BFs=T1UiDDyI3L&-6cTbwOJ}a)5mW5?^QH5tr8< z4m&bO9SX36L*QKxu=PUVJrA%4LEwE4uuVkZ0}rt0K;T0U=34NP2QvU7(T@SbDu3cc zTPw`^K4o&Qlrii3jLDxn*|rB}eP1y7ODEfdn`I!Ak4f1q1DSk6%4QkJL!48stWAg7#wp}(M`iBEHy;(1^^nVNa9Tu6s89yB*M;X zNry}ie-~@;QW9ae^&E#xqrHo5|JN0~tFdx4_qIrZalxRMoizLEs z?Zpx;By@>Gwi1g7T`JH~dafc`S|T{EG7`aYT_zD6*X0tyaa|!19M_c+!Es$B5ggam z62Wm@ySC2qi|i{CJ&La zSsE~Tn3S=w7|!GoQigedp2;sr8QXy`GWjJbV>|F=CMOB`J(@37ipNc0ti!{yhjBI*0s@_WrEO^CiOWjwg@29w{EG9Fyn$K9 zM}-TP*x z#pGNkn~wE9jKpV5{#?ptrN!hgrEFGOOg<)M4CdoZJ|ShZ(qi%{DVvoRlk=r)R$5Fh zbh15fv(jSn*G{(QZB|-L{#MFnrN!j$q>RD*J(GWsG6wU1nf#-aF_?d1^3PJnU_Q;{ zU!;t|{40}xlQIVL?@azf${5UlGWjnlV=({Cj)ZnOsWBX(XS+S%xq_5oH|}C`MJdB>+|A@lQik2A>}6WSARIcwsv_x!i)n6DO(J^xBU)V|dix_< zLn3HI{J#IQhqYR_3Zt%T@cl+ldZU@#iu zla-&MFAfp#({uxeI*5ru+-GS94s+0C|8y)tT7(0dhNeAogv0aslJSeriSbJj4%0Vd z{87d?GyV$We=`0zBqPT^S=Kcnz` zdxGtO!%O=t!njobuWdiae3qbUl>pDh-htK5S*%DBq8(H{*4lbJ=gOvQ)Q#0?DB~j; zAH{eo;}aR5#Q0Rkr!l^o@imNRGQOGdEsSqvd>7-{jPGOoEyfQr{x;)>89&PSyNn-W z{1oH)jQ_~^?~KP?5ME&X8bb7%QIeq@v4khW4s3AH5sqR z_`Qrj#Q4LEH(|UP<1H9(&3IeS6Qb=AZbtnc6OXuK>lPndhk^90!b^Z7(GET*y@`Zy z*n)|ScVm1Q+Ea!SOR$%Blfg5imDN#>1$Np+8;dpZ^cfwa_H_|Nk@q`50E_dz&cx%R=V!SQm&oKTh;~g09$ao^--5BrB_)x|tFg}s-DU45Ld^O{17~jJ9R>pTR zp3V51jPGOo0OJQ4Kg{@tjDO7d7mRvcoyT?jPGGQhw)b!e~a;h zjK9tJ5yn4c{3FIcX8aSzk1?Ll_z#T#$oOf-<1cpCk!Gd{H=}WH-ObJk$9CYIwHiK2 zBzh0R;iYcCczec2GM>WtD8^G6AIX?hjPd1+uV8$o=k$&k!m(4?J>l3coXQrL zdog$UBy`6c;qWwa z7=MTH!;F8%c-*C~Q6td|gw1j2El-4-!CqphoY;ap75ka~j?!_n;jq`~olb{58h+F}|Pij~V}j@zR&Y+N})2Xty(Yh3Wxb zgYAT!=(F+)SF!@Hb_LQ!eunX98Gn)SB*y15p27Gpp3|$J2>XtCd=hTI{H_@8zx<&X zZovGh81BIQr5J44--^L{{tql1WO0{MIP8o`SH}2AghRf7@eIa)XFTqzSo$jw4v&8m z~O;cD|($4$t=- z#xFoPeD&yJmgW-1FJsglZii}rb{9eWz zGJZefjTrCCcz?zRGoHlwI>t9Kevt8_jDN=XmyBP3ZS1^mL^$lX&oSPS@fR6?nenlV zPiA~B;~9)^V0;tfM;MR0E>@pM5f0nGCF3tMp2YaijQ_=Wo$F)Axd-9!e48@l{mBhCXKMubO2EOhw4Mpkk-j8+fP&0VvIqDa1c4L}@U;g5 zqdeGd!DtV1EJ*XbYe3U!{vV7_C)$F_kMI1n}f zUN2av7~U^fq!?Z>$WSbU*kZ--5XBP3@D#;T#qb!#GR5#5#d5{)AjJyB@Fc}b#qcP_ zD#h?D#cIXyFvS|h@HE9*#qc=AI>qoj#d^i?K*a{d@I=K%#qdbQCdJ^?GZlkV->ev% zdX{2v>RS|pQ{So>9<11=7@n-yt{5Jz*r6Dnt=Op;97(U6g8!Q}Tdz6lcC~_3T zQxtm@!?SL$fQ3eDKf<9=Jj(c=jK|&J($mWh2peP8oD3HgaJL8ewj+T`9<%_UM}t`Q zDqeiTrmpHiD+{W5(9w>j>K?SUDQbAo&VrgAw6~y^2jSc0wLR!)Q`GUGvjufMNVK4y z2i*W@QwPAb8a)K$#U3_weGi7(THohEPn)8F2YoGQ=s{q?{T>Xm-~kVYS@56-VdFP) zz&ss#0G9S4iC}0OJ7n$A7CaI2uw#L_O7zT+2jP`z;z4+2ntFgww9zq|d4TV<5oqp! ztr4(mco74>O<(%rY z%s-p82|Y37N(!41{-w2I@Gor~3&KnPln3FJZtFpKrJr^nJTyE;JI8{kJ@sci7-Czi zy#rw`*zso_b1%hq@L;Sx%yS;#lV+5qqX*$Dubn)YXH#_cARJ&_JP12;q6gs_b@d>u zY&Q?Ww{^RF5LT>*2jP31Q4hk3_4L40j9xkd2s_g-FXC%hG+>7_ah?-x&%{(?zC%Gv z+nfs=FwasSz?5R4L%|icB^G&rFEUYz3=i7aqbzp79tA#qi9@zT?#D-zi3fXxQynB|bEz(LrqEe-|v z;v#{q4w!8FFt6C=P%zA<+3rC<3wAgVjviR6osI?lZK7Qs47Xsn2QS&{o$Y`r`yfp4 z9*JOra~ui=*yHT=z`TY`P8lHV%5gVR7*5t!ZHIXR9W2x%N>8EQp1|}HN;l7nGrg43 z&BNkMKUeAIX>q2XuXOXcIMXjwx_Mrl=@%>AJTT7mOOd{Pt#tF$IMc6Hx_NAz>DMdWJU7nt8v{Yq!HKpU7mg-Eep>%BL)ns}trQ@DYZKl^zI$mq3%k+9m$9paJF#TSoN2mQGCX>~!mRcEYf9VS1v{G5)(Uy_?c8 z{<|~1hte_rqfGCqbd3LAOz*99jQ>7N@2hl-|9(vGuXK$60Zb2+j`2T`>4TJx@jsa9 zLzIs3Ka}ail#cN~oarN!j`9CI(_c_J#{Y{W|1UE=N$R^W{zo!BS?L)6DNG-w zbd3L0rjJ%S#($dA&*YeD2lzH!!n8Kla&s`GKJ|=l@7x)jp@^s4#P5o>2rnN=^(at<~bJN8>^IIKFhGsiS{Gk zFa(R3oZ)0U(09QQEN1!=rNa;`W%@Fu!w@WI`U<7P5UgbSDy72^tY-QerNa=cW%@d$ z!w{@z`Ua)L5Nu@nCZ)p=WHNoT(qRa)n7&2nFa%qfzD?;c1lyUuL+LOCJDI*q=`aMl znVzk57=k@a&rv!I!Ct1nBK6%c1g|pvHKoH4yw3DDlnz7iCe!yR9fsg7rteoe#_Iv5 zA5=QV>)T8}q;!necbI-y=@_p^n0{307_aX#{XL~)yuQ!$50sAa`XSRlQaUCAA2af;@&6Umk0~AF|2Wf6C>`VfB-2kR9pgWb>G?{> z_%C33q0%w_iHkwY49h=E zKldiFb&;J!j_N$c@RH{FieX>p0>$u}=7oykJm6)@_NN^)A9zzU^H$73-7;_ML68zDW}qAQ2z3Y zVI#4EVz>`@mtwdNSWz+D2fSM`>_S#j4EF&mD~7>b1uU#<9i?MOu&!d*IIO1_?z!Eg z7sJ+}smVSF?M3$J-&gu`q8h)lZ&eYJ^V=&MZ?LtkyC82V~+ z#n4wDRSb@@1z32^TOu6Rt*z(uc9_Zs2i1=0&nO)Zsy)-6RXX}w2c|!#bU3JvVBt0G zp>*`WsAA}UJrzU$>!ld_UvI_G|N1C~{?}JA^uKv`0*LaIf9ALd(nJmlzuITi7$FF&E|XwAncM! zPQ=UHamTZh=)=MET!*3~4NX=w+0Yb4Qw$xY=qN)|6-_mCw4$R8O;a@O@Xj;&%(?-7 zM&aYL^qI7vbHxSt*#~NaF&_MA!B`J|vS6GC_+<#nGTwvJ7EJH}KMp}DCVKFz1(Q6$ z4?|Fj$qs}UB*Qeu6h#*sI#tmnhE7v-siD&qU1sPEMVA{oQ_&TM&Qf%xp|c$g@H-WB z0&_gTk5v$u>j8eMg1|fv@Ou>m=6ir&t01tz1N>wKfrTF62P+6H@*rYCh6A?U_Skk? ztf+0bC5qa1TdJsSw`Gdjc3ZBfZMPMQ+ICy1sBO1Zjt2OR4LYOM9^i*I2(0k{KeIt# ztq1t64Fc;tz%OkOSnmOTZiB!E56-h-qX+oK4KgE}07ARA*@=UVG1#-oT;VK7%|{as zXBxQ0LG#gs!|Mprf)p_HGusdjkGtI`Hpk7`W)88#Q?oai124W)F?jJ^lI=md-Hurc zoDj|S06!Z-DfR%+M+)38CS#72;}eCS*52z91^9sxO7V&VW}BkyJMg@(dWPE5r_T+2 z%^fRjt1af7UU$^C)pi5laL~5Z2Gdq=0)}n158<$_-tvh}TkW;RaKERft?12$gy;ds z0^?}t=ZqY%dn|h}jd|O#z&M#k)D-l< zJZM<`_YrP}qtVgh4&yhHJ^%{O^dpw^6Q9(a(_YMgKUEAf;9SY}VAl1SV?pRDKL;>{ z&}%jbvoo4*&gjd3QJm3Nnwm2@1{$8xNrYo(gdfK`#pFUS+q1}^gBB@aJ8!j1uJFMjjX zjse`P|J|{`%!=sQc?ZHVfETm>bSy9vAbN1#1N@o~rT7~FZ5*7UKLq~gfVBgM@M~TF zfQ3yOSJq`XgbeiYGJss0`9g&8UHM!n7b9k}(P9wcuwRu%*jyEy&WR)pl`>4f3So2I zp_e^`h5OZBk3?@nI6Us1jFzn_UziSgwG+&+pQyjxx94$3gNKf z6B+O7IsMuQ!YC;>z6w=pfQts^d8SA-U}*H&TNi&BjC;3^B!JixDDQHpdA@M9_j z#(01q!Xhvhz+BPmEEwlg;J2_S#dx3M1`8&5fS<#n6catT34m_M0GOUY^LB)B#e?Sb zhf+`Rpp^wvJ?LSz>m}YHB$p|DkU@Y@%o4Qc3CBRUnGdZQI0e+)wHI;UDcl=7m zs@A1)KdMP74h9oA$5kybkEceW=el~4c%Bo(dUqpszGL=TRoamBAiUm}c+fiDoc^UA z7)wH*{`bIifJpRmfbjZU;l=RUU+F+@q;EfJxvN;>Ynd3%2}RMDCz*JM7Z=1E@lGa|^J18@ zJQFK;v7Yf{Grru;6YFOoxj^D*=Sn6BzU8)T0}s{xo^PY%|L;ZUsM zL1+PLI&hl0X57b^0oQWKOrSr;1h}?DFxzz;vKg_ssq2vQG4%k#ir?eJ+=OnOhEeK! z9SCN`n~qiAgYfCt1|Ebd8aiNl@QHNn5Z(_Kmhq6&gC}fLKI}o*T90@TcHSl)bg<3W z6d*iaODCFzN!1f0N5SJg{x1qo_k^dV=s-wY6@K?gk1>%1u1>U-pw&X!T2Q2g`)wmA z(tvrGY*xD2@QyLD1SEea_PoVLMRL zjt<%mlMwCXKrTHbLkT+jIBmrObn$>TE-9Yq;=vGHzJzF32XZ4rkfEE8Ck!!1=4843jVVF5WZwG?V1@`eE^cj6U2zzEf55kez--EDc z4)6fKC`rWz9^m&R2@LcAKPpLJkOyIF4E6xOF-a+gcz_?8Brwzi{LCbQVIJVOCJ7Ap zAZ(5i9^j`ZDaG?1;P)p9yx>7+x+-UKiPZx9K4lr2s-K-pe9@N_HuOs#gbn?&2d1Iv zN9F*`0H+6i5DsSuqdcd{o6BSdeiiD&)ydS1^(p9w&{@vOp3_aITj+?GEtpTGoJe0w z|DzVU+haP_IE$f>afcGxo60^FY{-f94fH?CT*YIX85jyZ$jLI7zcnn`%&ku*;RjwS zD28Va?g9(X|IXVe9G-teghSrUa~h-wzqSsW&| zAB)3a_GfYUfdMQI!xykPJi@?Oyc%4=AjIudvs~5k1O_uu4PXcZ)d7YwPy=8X12qAL zGf)d)1Ov4Jo@byAzzYo21$fbe66L5RiM+&#sq4#}n93$`Vrn~*6I0z}PE37MI58C- z#fhnLDkrAOqd75kPGf{xm-=-&i0R9;o$u5?63TVL2`yltGr&Ryx&SOkt3HsK<6y9eR) zc!vj}$=~TgX!3V?5YGB`d(g%jsB90+D|)n7??Jfi$?+g`$9p|66Ghsn_n;#k^h{2! zdeA-+7x(;GJGobVNu6!=U-KZ*g4aC=S6Od(5YF@8^dMYv@ADvBTD|3gSz0AT_j}Oa zmU6&@z=DGw46@*D4~AH9$b(@PyyHPQn>g&j3pT|O56p6ke)Q6VuuHt_LD(hU^C0XJ z?|Tq-i4QyoyTpecgk9nz55g|-u?Jz7_{4*-OML1<*d=m3=xER7GY`Tm_qhj&HpLen zgjeoM55g<=l?UOKJLW-n<&Jw0Ubzz`GrqGPejLl7PD?0UuUYl$}~Hi6iUisAPX zZ&GX$v9gL`&*o;uuxWFPV%W90RWWSa+@{!cVz(qQ*1V|@`}wN zRzWd*Y~(J*@UfAKip@WhudNYuAYW@f`97XTXEDFgNDDPp*aE6RCB^U|lgf(WY2zwj zq0#E7bUba`iRqo4Znn=3V2#+tA+x)6d@IIOqGL809!>4aj?&%9c0(+473n>g9##4p z(t9$!m(tgf-ka%toF04`Z&r4FJt(lCp9f!9(BFd-0Q9mEK-emQ6T|xAj^aS34_5j% z(uXkp1*Zq0$9>U*A#}B=!CrD;Fm;2t{jfJL3v>YHCdngX!Z(kkaz=uM^-NYe-UUcu z`Y5I27GWyWM=KqV;ifS?UFletk74>)rQ=w%ao3H0hTj;h0^i(=1QioQaauRSk3e`O2Dfw$UEIU;9Hrxt{k=?oMe11?|F1IrHKk+xzs~eGl#cQLCe!yRJ?%^`t7g!dY}KRZ zlld86e3wNp8L@)*tAb%z4lw&1%{!aDbtXVE|%%&TM_4ehGjmMhm z3dOJnx>7N$->-7amN5bAi;ifDTbkvW>Wa~Cd zmx+6VcROa&Wn#mrl4RS_5i2W(!Ba&s?qkZ82(O*;U&V( zl1;}(To=boTNO-)Jxz4Xw##(%gRY994|S7lCaz_7#jwfPL$Wz&)2L$D-0CUWT-dc< zilLo*OEwSb`bf42ZQ55c^wWNdp-=XgY#D6W0LfOs?gom%UJaCNHTv2h$84LfMY{}k z%(m%TjE^CbWuh)aCCfs&!z9Z_`wf?D4>ov5NR|Wt_Pk@ZE;(rD7X&Lz!sOva$4vbS zlg6KEqEI_{nI;N1T|o26@x@=E(x#j$44vd<#n2c@l8wa0A1PTf20*f8X_!2uD2Bm3 zN->m?su=EOjFv1NGuAZ4(81Cb!{8Ys*%)-rv5LWQj#CU5dc0(Fa2H^LVlZ42C0mS1 z#w5qgg)Ur-I!{&%?J~tNd!d)36Hj%_=3RjfJIyhhZY8eKbjens+!>N>!X2lXl4at~ z$}Goh8JU=9&34R|k%=~#BiUwbxX+brD;(lH$L#U8!XV9e%pPwWuI~cLcA@zm=2O=sQ*T#=HlU9v2c@rGnO(Rbf; z%=W*Xu(A6bvwdwB^1kJmO}A?|vHgzObh|N;KA;$E*g?h6hu(I~9&b1L-yz4sA`K-Q1q*b)V(@kkC}x(xiltM! zMv7r6|BzzvsErlFXnt5Rc$h~NgO_ch7)E$g#b9lkDTb|@=88=w_NZd8axD~tWqC}p zRGeE&#o#?3R}8baCltd>=t;$3ty?LE6hnXRs2KWlC&kd8J1d6%+(j|myi5dxVI;k)(~V(0 zK59C#Zi-IbOLtpHv82Vi=#o*<7D~9c&K8nF~_LXcDJXJr*(lE2` zuNY=010)-Tbb(@+@eNcggV-R&(2oZzhJHLmG4#cuilN^PQw)7X{sU-C!R%`v0D#qo<{>Dh!={tYR>V;}nBY9IqG#*#yPV=_V?Mn~0OZ z%wUZ~CnFrruBN)QL86@^P4l3e1=AfcF8j%&224OYgW)g;X6U$-7Lx+mjG#NCaX$ok9rm3JMG#w3EQqwfhQksqd zJx9}Vpyz5j9`rm-CxV`@=_Jq#G@T53p{7$nFVb`>=*5zLy~(uMC6a!VJR4~*Rdn1k z(9()7$ObK==*o1^%M{%-9rSWZzfE2TdWE7hMuA?b=t6VGS1Gz_71Ca<=*}&m*GT%G z6m!PcDmva=i|Z7fVcOw(Nxxlc&iDpT(FMzWJA2u;8K%lN{!94JJQ5<&n;?Wvx~xxR z`Y!HnQ(bRXl1GYlyClUe4Wbn#&8M3mMDJE~zPY}YBrQra{k*cId31}F zj!;F?e7dnjw5p;RYe1_hI^VQ!bx8}B(vDx{{vSy%a~NCu!ak(;x3qbo~mXy;stLdFBZ9B|VjGuG@W*7G@a>*Fe&| z38qIil(b;E=~4GfdMevop$8-_Of#+ipr%=h~OrqkbJ-JeX!LMJEqtTF}{pITm#BV4eku9xSz>s|U+0=;pyn3%Yx- z+JYV)tg#^K!8!|ida&MtULI_)ptlDbE$HLHCJXvHVEP&SC#_TjgZHGBie%(FX{90= zc}`lXNJf5>Rw|N_*QAw-VDOo=Qjv^2CaqK?BY#ON70Jk3(n>`#@|Cnw5e%M^Rw|N_ zpQM$FWaK4jrQ#Xh{r+~-j<2(@zQrP<#uK92bD+NzTYZJ##qg?nwTAMg#(~%dXwTWXv4|}k52W%p8f3$vZ z%x!6o^p55`z9$%DQYO!ffp2v9wyIAH3QPfm?)kvV2g zG@o1=-69hV4vlV+IcD<0ozaRyFgP<>Q3wWCMk@-(Oc`)ww4!j#9*^7@ttbR5ScwiW zSF-82Jv7g;pr1Xl`3{(a6^&d?i53W!Hwq`aP_hYTMIl&z7W#aKU_}|&fm|#YJQm#_ z6D)5uRyRup%U_ROfn|;b2W^Fyd+@deD?B)4!Ab{Ao8@iCM%OCICSl`ewPUs&$KzJO z8p$?bvAtF@a{6>zOfa~7x-BLc96sF^6AbR2Zi@*9XHU1qBwL2fnM}uWBh}%QHnTXK zQWlHXq4*XShf~@bi`S@fChuD{_=Z~U^|SWA+gL&HcH3DT-fjnr!`tm-@fsB0#p3XG zyICA3p3UMo@jWb#6VG9BocP{Yye7QyD=u#CBvi#@nQod1Sk1!M1gvi1>jKuW@C^ZL zTKJ}bwJh8xU~LQE60nYi`vt6P;Qo-h&be9Cg5qsFTUq!h2USIN$da!$^5wF&K#t6oY~JP%&)leWVy% z$;XOeyY3UkGKqbv7)E@qVq1xQrWmZ`=Ze8Pf1wzx#g~fhA@-GGxOsX^vJ_Z`^IvP%&&Z7b%8S!qOe>rA6)5#P#+rJe~$WnKJHVO4i9_oJ>hC+H54!F)=JkDP_gU;Ilc}D#-WPhs7Da_nB~xr;Ui5Eq zJ!P}?xY&!f9_vn~kciW}^V|%C#rh##?WxQgmMt#$0jh?a?7a%3wc89Bc5_X6b zx_V)&UF~&yHnD?W;}x4TRLqgH&NQv57L=nENX3iJc2nV6RWMbSx&ZYl>IG<(>%4B8 znRGgFI%SyD>%C~tClg9CI@}FPp{q<604Ds#e=Bq}O!;r}iY;m@DuKR_kyh4=wt=?V z!DOm}t8=s0ZRYK!FN7oU7BAX*?zWjJ+pYhv0&{VjR7~fw=XSeSO#ewqJDCiBdWXVe zY{ltWR$@Uy^CKbJIk7-RJ za^CF~(`YG^O>@$4MT=MRqCM<%7(TK_uCrJ6x;-rE;kByb7280h;FPN>Jk6F<&0|{* zEV45-FnQIzZYxRpQd3WyTMaMT%rntm$=gpRue1lR>2+Jl8Rp2SO)an3oU>8=&?~lX3*8WgDQVyWS%g6c-SlU`Ykk#O2eM6JgV}D z*KJi6neIup3t|&5+QwL9U0zdI39b^p75frJBh0)&yyb88c>&= zPL&{~l~?T1NTIsX07E5Od(j?^&KFj=jmNeD38Q14Ox|p3_>|XeX=E@$*Vxu8co42i zff*i83s}{{b^=zj@EHNCTi9N}8Wuh)U`-1<2w2O)=LD>6VMhV$SlCIxx)yf!(5`7M z?ILM-PKlC+C)HKb@T|H?8lF~nNyGE%A!&GGQAxuy>nUk?YP}>4&#kwh)xyiwN7C@z z`brv}TR%y|bL%f@cy0q6%}wahzY}${z=KF4;sayxPJ<~v$j1{pcM1p$_8`&^i~k|9 zc>gXG9~z4%_M!MNA5Tc^Q;ooI4yqFv;Q{>&G{v8HakC=H+nJT0KAG?f0_V?OZx<>r z3Y@oWCG7*AX_i+#s6n&58D~Aqdr6d+KYL>SW-RSr_83E)HhlB4C!NZkYj<>$oR}Li zjXp9K?~6uHc5$P=vb z2PJx0GS-tw|8A6GTrA!f@$oJmRugQ3V%Q&==onQU@kz0GFT^Lu;yp%Ce2R}J^fF*- z4D>NzS`2hIV0sJ;GGK-SVcl?sGaaLPAU-P=PegonEZz(8Ik9**blthJcwfZlxp-Ix z*nG#R48#}2;$0A57>oBpe36eQ^fALBBL=z{u-Jo$i7)Z-gkAo z+v6A&f_P3W-V5=)E*^BXg}mZ`IY_S~nCHAISRZ1M=xbo%^DVDCJvY*M0HuE;7ViSf z{U(d!676$wV~4xY1)|YQ--ZDW54qnZ$&Dn!$Q+2pyAP-M!C1TpjMm#M-k;)!V)1BS zioX+!_v%IQ!!8~?X3zVG1G$l2gDAn#SiCoe*Sjo^1HR|u3BCKl_Pp;wr1vn2f8gT5 zJ+_1o9mq}S6NOdyh=G0pAA1n#--8%3s{ZdnY?3-;6-YN|DCmWJRwS} zZmV!oz#0~w60oL)c>>n5Fkish78VFt$HGDZ>snakVR%EtG|ShLhK==&q-ZT_!fz!F z&+0#thNtzNq~UpeFKKvUKS&y$*?%PsPwhuZ!*lyd&}vm|eSelTJh#)5hUfN+q~W>! zDrtCbzeyUN+wYQw=k|xB;ko@OX?SjbNgAHp--1>T&+UJbhUfN=qqzy)X>vm^=hUDv z89u8zVVN7B^lUG}A%BbUql{Nv;nLGfDG2*#pT3?ml8${pSol!H14_qNy&hzGBcA~mr(}>NO*xZT6NqkN30!N~cGQEY;@qE@}OmFG* z(_|CkzMg|GZ#~ZRC!8K0A79>jlIg9KjxTSuW_lZ? zOmDAre0l3xrgu;}zIOB+(>p314~lnUdS|8M%UfNTo~U$u(yuGiyD1%?^y|*_9!g(L zdX(usm5xvP^H?O$`vCo7pgUg_xH6PP|x>FD2+m_AwQ=9869Ka-cN86?w)=zcNTf$^BV zj-fwY=%4?O9*>B_nE8o5gcLU`u6tZ!{F3-5=^^?SE>IQOrNWC^DRuK&v$x|W52+F4f(`zUlFNM`)dM%~ni z_bDA;v~0lihEiW(d>_*vP`dGbOmC!gr-XvXyBN;mG0 z=`ECQ+#l0hD&4q0raz%{Bjvry@%3``(t`fr5pFh^xjH0?vLqxoo?;R z0*vo|Oz-dXAeC%6i36B8+=WuwD}E>jfB)FS|Tv z@<+9T(Nx2uXYG%YWWfu~M8FkH6-!pSnFug_l+w*afa#-^ZYBauAE)&3bmHTgKEdh1 zDeHbFGI6pK?Uh()yg1XRD&06+rq5Kmai&b4t#so|nLbzP#+fpGzS1!a7chOH(lHDd zF+D@+7>0|PzC`I5hD({gOz9Yg%bC7H=@^D9nZ8Qt7>28vzQ*an2s>F=>%mtRtn=Wc z1?wHKpP*kyU2KCxL7u(D8v(*q)+Q&KFR&lPLoS&Tr4icfP>^qn$#THe#}NM1>ycpC%)?>UO zYOW;44>Nv*@m$8gWc(k-cxA?`cup^kA>53*WH1Wz6?$vT0oyOvZ70^jv7n=U z`{6kULL-Tf1a$-pJ8#tMSkxZGB)X@U>8Cvqrhh1}zfVf##~q)TL2Q6xi-{q9Y`7ku zxP;h1#qff}AjOsu8>|@iXNM@Zg4j^SRuUVg*eYVf6~luZBNW3UoX;zUCpTVD41VlI z#nuyhNwE#YURDgx;Up=BCtpV@hR1l5728ZKMX@YmqZHdhELAZ)(mq z*h0nN&KD_$C&V%o!&4WF6`MkAiDFZUEmaJ2t!0W~uCZJ(JpR8zv6;kHDmIJQD#h@c zz-qgmX*Vdg@Jt@EPB4zF-;%TRko2~fDh%&4Y*Gw{ zHB&Jd*3F8+ux2R+!@5N=7}l+d!LV*q42E^PVlb>b6oX;isTd6FF2!J2cPj?NnynZN z>mJ2mSaTGEVcn}3R`sudg|m+D5T<`v(Eq^rAB@MXi=|)oaV-5Ngu~;!#rWHd|K&No zMDaVNZ$@Pq-&&Z70Us||{yZ4vh-1NM>y#55uo<(Ev4mjxyD<_=3U+D-JV7bJ@+RRz zp5s_B$rf;)1Ezqy&&YKE1V2}bd6v|sp!VnO12Vy`Z~c*?Lu9y7c6fA{O=8d<*$RExlyo!bkzAK z!3t+%2$U79Xb0+ivtW5!VaIO~EPpSC*{y;V&VsMFO|s=M4Yv!HKMp?X4#5i3V2kgR zECZ&ioMYyCzN%zv;mfKCR$!h;tS;F+n8F&41uxl=R?`7v}>NHfVYQTgVA z<*!0_dsMRRn8LJ>EF0$TF~JJ9pf|LXYzMmj?QO0c{% z^w!pjp?9?rtY8(=Jtf&TT)?)16;6Vwd|I%gIWUXu1S>SZgYt}E`Qy+A?Hvmy()A+n ztOKU&7p_I29UL<~^VANkQ=W6oq|4ig@!QcclP;gWnM+@Rbj+kHn25Q2XUS$@t=2`b zyz#JWiH@1$6)eTNr>kJ6axe_L36_@wf7xBIQ#)XvdkB`Fg@%txHUqw~r(gwJ(9wDc zmNyA&oZgZxhE41vSiyX(nfeM=I0Yu5pJS$73fI79_ZKXG15D8X$AUC#CISa+BX7ne zVxVN>aM1=iW(ydHsmx%4t=S&foxFB&veu!3c{Vxt8snu%FOnq;eyE?u%+=-p!^+l`gwSjU2%_G*uFz+SO= z@POkbTMrvGL9oI-=&}<9%QK(EnIu^LXiRS=OJ;sVYl>u3VMC`%mW|cWG{Fi|(F3MS zHWh4!WGmrhW;$kiXu)>O=4Ux(&bM$Rdc|zXvaynyBiS_c(7A#YC82)vB%6S8=L?pX zj*GECva!g!P_n6*zAh51z`X31Az0y3^t{E61#_)kUE)C4^Ux?uCEJT`yiBnC39xy~ zCEI|LT_IS(CiKvil9~DOD#7R#bxOBdu)KM&=W84bX4(p`b--458;sF9!Sa*frPd2p zFmdx4rUmqAl!bKkL1#TJ*dPm=vYINeQL-F#)lGsGj>NFZ6s#y6-EFgE^RW8Ll57DC z>=wx~Fdf+{*r^=2`)z_1tiYApE?CiMl)FQ+x$rtW1=rC91?QVB z*+!JRN3u=ulsS%>A(uZBE4RIlxhcRa4wx%nkT#JLy(-xp^zqjOE1Z^1>~+D4MuNTJ zSTNBVwKp9w1?0~~3-1%GXg1E^Ey41}V$AFpEPnyU#{tO}VgL4^WA;q9!VSIcm@Rh~ zy6+*u3MQkBcO;vERyr)%Vypp=NVWyW^r&D(W=`;~W5F@o0Pi_qE0T_reP6JmIp`7} z2$q+GLO+yj3o7@KVENnOay}NUFdOU>!SYO({Zz30R4gZQCCh+?{LHanmNgKcJ77+t zXfFEZ7lP%bV|;!o*#xdZm;au4%kMShXTG4EN?sp;fTUfiwXfAx(kCJ8L%KjwTE-W&C7A$`u(w&xUI}F}0 zf)%FWV*Kh@@PciW-yATFQm_uI)88fAjH%uql8s9u_NQd#hbjINtZ+RV;BUcB?MB}J z3062B*8LyB^3u^zaestcji;$+$K|DCjS}yesdL`06~rQf<&Q%TO%SXg3#;}Lg5_<% zjIyL)`CG77C?(lkc!qNXD>T2Ea;{^+NZX+2IbeI4`Jt-w9Sc%yq6-|bXRsT!y-+gq z4fTs8o0UYHyBC9nH!&}Dx^11s_!W@SOfMt!{5f0BGy$j@ET=a9;w&csk?3Wt;456g z_5{<=q*pp-DqAofR_`jw=3x$awPPl4!7})%Yb0Ba<6Wy54Apgx*)mqZI9xAS(E@aX z8yvG`Y{nGrM#q8|ah2#J$^cFNE0Qt$&Dban7nL_hZQD_6lY`W2Kfej@~+f3|!$;_9A zA8^bb&$zn>9ka)qi$-ZA*%BD6ha}sIi`LjNd%PW(dOqx!Jzh3E-Xnq)q~hW>am?f` zNXJ~JsbrIpx0z&1P`~Dm1t)CdJ?elx*mBI;S}2xI>@mgQK3h6wDqOG}L-ldTf|Is@ zCmgT^tU$MWQZZbKR*u<&t%loa?U>EG22;;Aj@fh@k?twSf(3R))z$%uTTZCCw zPsuW{Sm@=LDIk9&7Tvu)D?t;0AC`?nhwbAbW(9@mv&nMy1v585X}3=3r*>e5Fu*ZW zvAoeZc;J{RFwYF?fr6dNL3bD=SY9fw;9$u{Bi#_kY#HM)TN$btW;Mec3$kph4R^p+ zWX23iG{P}^FtZ?h-Z7ibSdAAP3zpKs&g9{%1&1j`;0O-+_;HzuDcieX(fO0qpz&ZJ6~gF!V~u>2HENYflMEt{VT zYmx4lIk)^#=tvCrLI1 zXFpl888C`d91E7&LZ^B#*@9^f*mImek8(|S%+}1zgl0Gv4728SrU&!vk!E?Y*n-&( z*u(6`q31Yei`$I_$Xv&QY@2bO113>H5@vDp9Sio@L<<}+M=w~2nZ`oLY{qnq-9?Vs zbYn3)%5W@LX%Dv80h@6O?#V21%x0X9#phDRV2zeZHXGBC<%(fFw!*Psl`V9o1GWJ3 zGdZgqvlTIqN351?0Vcm|9J6^Bz^bfu%;sH+dn4-{v+360#MV0&ykaY|!2z2n6KAzi zvaPsyn;diAF4F;pi(aF9N+BgjQUv-weDm#6RFi+rh2eL=| zcfa3omb@l~-gIEAx<3!s?OV>0*QLrDWB!dXPf!7H-Dd*FTnW%ojapI zK_5_}5Fy?5;luUf^^?7(o{NvL7f)vw!H)DW%DA1{Gxy<&50P$VW5k_xxfpSiP(H*> zcJ&G&uJ{mNP^$>komLA9d_3->6{bLSpx$iP2n#Ix2sb!2iKd@QeT?zLagLIcr0+VO zaI!!x2lg8_A7dbLLP%);YKMf5_{5NqjVFbK!OF=Y%`?2^k5Qkf=AL0m$ z4MH4gv0;d#EH(;pv_bQIV4$W$ld!;8yH?W>$5}i(#PJrJg*d@t^AIN*G+z=1>Q2uM z3rw~JT6hIoh6VEMX3q<8s$J`RZ>O*2Q6gS$3^fAb2^ zF9~t3#Y;n+XR%F)qdr3Gd0B|VK0=>*d59A%wheKH#VbP0{TP=|J0dRD?HHY06%xiG z*M)>pM*D#BG5onTpzXMSx;-RRxN}H2uzLc^$D^JHLc)Dv5)wM7M?yk>@I*)$n>-T| z?)7?vgeMcdLc-XsPe}IeBP85>3~)xCd5x$&T#T z7xQtV^BmdsGao^l5zr@y(5#Tqz|9LNAE8|o&?gu~EC~r;VO<8&&mzBwX}w_I4~phv zaQ1OPpCAC+LqeE72??k6Ge^l5`QqCD9N5GB1cRU*A>oey%aAb0*cB3n82cRAv$(zg z;kxn$$=9YUuV{WP{K#&|zjFJs-S@Ux+iH`;vz`>We&QHDWc`JEijlBBf^}Ha64jAz+v*Oj8O|Z zaQnsZvrve0>`n^Dh@BLP5s#UQMrik3EJhq)@ffl9qe7f#_g*4K?EUB%vG-#lw0kcZ zBldo5jM#fAhmsrf?cPf}l(RJ7ZnI2`*k;)nvCVQJF4%_3myghHvqFdq?cOU!XiHWK zagi-q*`ZW^k=;=hhf?`PcJ-!ixKs#9wSb2jR@^FYlgVe?)|t3ZOP+9TxIuO zD@N@7gcz~++7a4qo*3e4yZ4hKv?WgtagD`OLR@RBbZUgQFh{vtf+bW}_If&Bh`2v@K_o7||v* zjS+`=c8oa8W)W`3SfY81XuZyf5%oMbM%1%Kh`sEYZy6)1bY6_8()khEvvfg>*!zVs zV(%A)*xT;?;ux{_Rxx7lts}I1zr>-vw72)R9mJ(EV()Dn+UtFLAG`O9%6*1!2+r@~zUm0RwyZ5UiwA;Mep&U_Pdqmg7h;3f$P>yJT-R5;M;)t$~5l3`G zgm&*Y#)u?MqA*ZVqv&#akT83FvR}Rv>Pf z?QenB+&*YKMz$Ry9c;B@q(iLU7U@u{w?~?5^^Qn~**bKBVmKm)Fl!qI#COJshjyJE zCI@8XahC&Kbvp)3cRO_BlWf8dvf4ckWVJbVkN3uiJ>KUq!80!d_dC#4?H(U+nEWE8 z9whuK(8YnQHqY+yp%}5p#9{KMtlHIqu4?!Au*2jpDb5O1o{xp6zEGhOQ4?vdkVJqwtZB8he>@YHNb(C>SNFGz!-6k2RTd{ z%c_GN=&JS{4{?|@ky1kmO$BlZ7YYn>Aln&Wt1vu9RAGd}q=l?H(t)mOt1!x8(o#x| zCY&cQhVY#v!dSxh0^=Om4VzmdG@#=hx@&iWgM((fxLd(Zvgr5W^6Mhz$ zLeNVz&w*@sj@|H7hskfU+BCxN0@De92+Sbpl{nLZYX}W*k`p$EkQSIr zC?GJ8P*7k#p^(4=LScc0gdzfq2t@@J6N(8eAru!_N;pbjnF9&jN*lQ44wDj6Y6anF zft7?~1XdAB3alm^E3k%8N?+LZX} zh`Zs<+*2jl^>c*E0?!kw2)sb3F7P6uhQLdNS^_T%1F6pdTc6Jzy4G+9;Ud}27X)qAz9d{MC3X_DecDB6B_(zfv}fPrz#inb`L-^5 z9VXf?>~mmiyluHHwLioab^!cUjA+BZc9>`menZe4{Fa~z^<j_&woLIg|qgJ*__w z+DM5X9oWAuusvtHj8A`xSq7*-$1Ee%gO26USKIrEUt+}l#IGT)wIzRx5hZ^Qah)ys zM~o=>XNb#f$-iPm$-hHfVN3oKBTD`o;zC<8^>^?DaN%~8%nEUVEt!rHB@2YO$d)V^ zBT5zuaiuL;I7XB#65=XbvS^GbSuDgA_D-&NjJT6KDn<-MN;pg|m&7^Rfy8`;9YP%w z;&O{6LtJU`*brA)Eafn{LUvM`&`zKX;YxwBgsTL~5v~>}Pq;>)0^wSLiiGO~DiN+1 zs7$y)pbFtefvSY|0@VmN2~;QCEKq}Ri$G1ntpdjpItUz3=xE?@U325)9#i7#GN#lK zeR8fA_jH@=`UJx50<{Tu2%JdhBybYpPJxpNnnb4%I!lRD33mycMz~wxbV9bk83e7z znFOuJSp=;|9fH=QF5xoSXgxw(f%=5f0u2a71R4@_DA|ahL&?Sjt!Wd2*0d=>YkD?8 zciW7hBfjPix|wREw$0v-Utc~KsGm(}#{zhhew%ryZ#$tYW0oi0S0BQ*^ffWdv+Qdl zHn)6u0(sq`Y#$LF%Jv~q$fkPsd1?EQ7n%oeXvc0kl=1xN=8%_flf$_sW_btT)d9@yV*F)HD`{6?w(dp(`j?z5Kx1}F(EJwLk z5B{-385Mjy>IIGuz$$r60sW@a0uIa4?L7n!9g+0)iC%Fwi_7| zv%J$a^bj_s%^!3D>s#N$<01=envMv$uQ@baM8X!j1DY;ZR2Di6IZr(+*{no*uE*d&Z%R z(lbLgXT;WIR>ayY%#PTU#zy9Zyxbfmcy7q1m9yKP7c!c&!#>Dwl5{cM)`lY5_~G6T3?`e^8bk1X2CDvf~Dr^L_hmx z@2}y4wPx4ysO7g{!S;S;jb3(BzjJ|(V|v;4;195UOYArth1vD?{x#jf)HKzw8?NbX z=ufVJzpHFcZ?BqovFqT0$Y0!%Jugc?8H7b91b<_Jp7zu~W;)-jZ&o-K(v5`fc~JOW5(Uj?usH(%*wY0au@36IL^*AX5U zxSsHszzqPqgC_)TWC;y?dx8f3Cc;y)%FTqQ1#SV@jXooAE8$sz4sMnFtXk$UJ7#82 zG_$v5W=}obbBLx%U$eQ|WzF7>=sCpgZa?nR>30B7@%*eaOg%ce<@s67&Fr0-*>lZo zXE&QvLpaS}bpvQ@?sg0Fvu-wJvoo_NnAv+Wv$f6ay_wmTW;^#`R=3l|EwWiy)7*fY z?{30SOM6#t9;*VAs388jN>`Ys=EKo){KR`VnC|G2XgSjTQJB9Y;A2t2p~fDM3j&{r z3gXg!GA;;wDk_NU{pq+M@R_LKaAVKL1%Ww46PeVusfN0TgfC3=3<mlJ|>~DvJk7aKSXnS`ai;ytB*&Px-&$2%xj3~Yh37-i5 z-cj2Q5ZGDq8&lnL4MZpX2S*CY|=|w?1F^B0)+@e1PT*+3lwpXpLLSiWKlQkh7`pJ z+OrfV^pPzbMbNJgl^|#bc{Bh`RMKBoIfjrcooq?BN`BT^rWVI?d0AOritx4Bj44r? z@P$Ab0#2AIQI=3nwo#5yUZ6aof2csuR_rNzA9mW9DFr` z?UKx=dM}Wv#}nEL)FS9#<60%s6937kpjEN~X#Zh<<4 zdj#qd?h~j-ctD^&p^HERLL$(R@UTE5!XpBW36BXhAv___l<<_m*@R~VngMJk_K;9E zCkzrehtN~tTnG7Cx0)-ag_})uq}!5kjAZ+H06#mCkf7Od5ka%#VuEHz zD}rW6Yl3FSB?QfmO9`4CZ3rFZ^jt>J?6{nu+0mAu*>MFyv!fkBv*SvFX2(?o&5o-H znjO~=G&`;(Xm(sj(CoOLpxJQ)L9^pVf@Vj1f@a4}0Gl1|e!xL~)>9_-x479vZx3#D zke~I8S>6G&h`b%*emM($ZpX;b>E7ln(U$#oLXITe9fa-zoe1{!$qeBg$W2{Oe1)sC zv*aY{rtfkfg?eE4cz0yzk+UO1hkQ?D7zo}Q8HRoLh1L_>yFW5)?}5m$y$2)1P^L>{ z8;m{VEcsKev4rrKKv%-w0uK}Z5$HzvSKtu>{`Rri%cF!WfyW4GfyW621fC!i6nK(Q zNZ=_#VS%R!MFgH96cu=uP)s0)P+Xup;V6L~gc1f0*E`ltR+xIL z*S!eG2=peD6zD@ZR-iATlt4d1X@UNPG6Dk#Wd#Nj$_Wf2louFGs30(eP*Gqgp^`u@ zp|Zd*LKT7GgsK7~2-O5e5~>S~BGeEVO{gg_hH#v~Sipf1XdH~3alZ_6Ie@_FR+fVKwv##p}+>hB7u#B#R8iMO91ANdjd2Q zUf{x|Qu;;0GJ%%}%LQI0tPpsGuu|Yv!YY7yF9D!?e1i*DOX)WWYXsgRtQB~huuk9| z!g_&s2^#?E>@5J@<9l4VQA)p0*d*`)VY9%8gy#f4B0MkfG2sP3I(r*H_n6OxFG}f8 z2rmhIN_bgT|IAx`hgK5l>PJ$B1XCMPkI$)S@xsd1|p3@kF(FjCiJc zRE!v3l!y_}RgaDlPgakK5zkgj#)zk@$Hs`~tEFPZ6V}o(;u&k17;)!RHb&fal#6kr z!SXTUCon3+h+i(L7~!xSgOy^$Gv3NEqK&K)BW{kW#)v1q)ndebXZ09y<5?p{JnyX; zBcAvk7b8B+a(s+<@3K~mcn{}<81dd@?HKXqPmsUU7Xxf(7kGM-)7w-+DhV?OG+^``=ys@+~MvMzK#fb64<{0r5;JFylKt3NM z8ps!7L<9L^jA$TViV+Ru%Q2#Xd?iLSkgvvw2J*ES(LlZ)BO1szVnhS^W{hYc--;0p z?jA$UY#)t;;y%^Czz8~XybHRN8)C+DKru9Pn%kxyBjGv5Y zEnmm;PM$wVH&1;q?Om*+$C#Qt6eF(SBt~4pU1P)*{BVr8g1g0tEBKKZaRombBd*}b zV#F2vc#OD$pNMg)IgL*O?P)P@7RLo}eLWo`uCHfe#P#)TjJUpXV#M{;Jw{w#Jz~W5 z)iXw1U%g_)_0>B@Twi@+#P!uTMqFS0V#M{;KSo?%17ciiPUAqJp2k6O0bEvtW5i`O zBt~3TLu15cl^Y{2t6?$XvKk)aT2s9dK&{@$xB!M5qhiF**p22|>FhC>)=O-f=hHo3 z=J|5Z*Ll9)^NpUT3TL+8xQMH5I=dyNb-&kn-qG{BJv@v4?dZU-2 zaUr2886OfFk_nEIe)`|Pj_IC{B<-8GeSbMFw;?@Z*R?VAgiHEolVmHVTe7)VRD9x0v z%k4IXnr$o(3GXni2nm_DG9)DSs(@b2F|;})+#0P32{%S-NqRDI7qTwK$)@V-fwt=A zsZ3Ebtu0$&Osg05yqM?3J#XZBW6zs<-rVzZJa6Iob)H}E`4rD*d%nc;9iIQ>`OlvJ z;`yJRS1pz~-Wr(J^KiE3Ej(}Od5-66Jm28?7SG@FykPOnehXn*_glpC5}ud!yqxD1 zJ+I_>BhQ<7-punBo?qg5JI`Fh?>i6(MWw?mWo*Li+}=UqL2%=6)%kMn$z=W{(@;(4k>Ca{-aTHA^oOt&%Jm_GJe zjw`%-jJU>o#E7fBXN-9K(JMyuGreO(qu(b+wD5gn>}#-JjA)zt$B4%t17bwOGcZOx z{umS^9)Apu5syEH#E9-`XpCrRb7RB+YgmjJMGTJ-kIhHKi09@bV;pI4RE(nxj*fA( z!7(x7rfqDDXza(uh+*FN7;)1!Aw~>JCdP6Iq449_Gh?};&81oEHjS>CX zv>4H&O^*=+(HSvfAT~2bj74X~h;Dm!jOe82#E3qBZj2ZN&WjO4uK6)y(6t~&+`TP~ z5qEEkV#F|Oag4ZoTM{Gg-j>FQrz6W^TxOcAhR zyyGz$e-_ia{T$Edc|PCsMV>G5e3|Eedwz1s%=YVGTIBo<0|C&(Vmy|yp-oG}1Z-{AQzo_F%Rv*-7F-o^87o~O!Y&hyQf z*7JOe=eK*F#7(rcwHSM2Cr*k#Nc&pj2OJGixGp@^)X`bx*w!Rw|NF?ih^;}kPmdJgDD zNSM|O>tD}{RLXcUOl$e#o|o{vtmoxCui$w_&l`E(*z=~IH}kxe=j}Xi?|Gi*b3C8t z`8LnTtls&eLdTVqt`CXo8dw!4SLp{&+e7NT$JRj$Ip6ByC-%&kt-hRili+hcXm%_AqY0sN^ zey;PwjhLGxubNi;qa$p^)7kC49WHS@G>ybk&!6-BN6-KDe0;6U0j10{aMxfwh-p30 zE}nPuJje43YiG*mo|W0oPndQmvQEb9G{|^MOlw1Ro#&|wGv4arO!?H^8E<+|##>@q zx6{G%j-KD+`2(JB_WU`|-|&2k=ihmL!1G@`Pu-iT&&8P5^U%)o_MYG5`R$(H;rU~p zKka!g-Tc|=2h6_OaD+5^EuM0m7bBi>oF5~ea$FE2o^o6mBc5_x6eFH;TpS~wajB5MN=P_NSon0D>MgBfpwY4s~S@8|hY&+|Or;rVXQ4|x8g z=cz84{r=WBv)@#|jDIx9^T8P}hH0(mfyEjB8Pn(YQ%f=xthF>#!Frh1fYkTAo#$73ey!)%dEUwMyFKsX`CrR2`%P`g?Dt+w>j~@P z`O}`~c;4UhT+hdQKGE|$Oy`?M(InsuuT1LQOdZa|wAP`H=dC<%=lM&Xr?zCa^9!bR zJE^T1KMB+7r_jx%gXuPCu<=p+1Cx4>G5^t)c8C{@)dd~7poFYp7np@;#BTe)7NRk` z{r_5sM(vLOYatr9PXE_JJj%Tj3pIthVpfTY*$^*SrwcZ6fgGl+ zp@+FCUTD|Sg`4Arc3);8&cSo>Lbv8(b0p7Wp&rTCn0A4DFXIoKj{>FD|6bn{X24@{XhCd$cW=Ec-Li=baEP80O2#RUlZ)#8E#{c3R`!gaFI z!UX+laS?)kwYVrjzgk?3pkFO6PUt8bJ&JI}3> zMJa-QwYW4vzgk>|pkFO6OVFrY#Tor;w zt|~zzSB;>Nt4`3!)gWl(Y7#VZ#}PDg#}hPiwFnxy69^s6;UA_uYE&nA({x93j<7p2 zp9kljG$l?XXiA(!(3Citpeb<*K~v&Xf~Lf21Wk$437QgT5HuyuBxp*UMbMO}L(r6{ zOVE_4N6?h0PtcTTK+u$ENYIpMM9`FIOwg2QLeP|GO3;)zo1iJtjG!seoS-Rj4nb4m zTmn+UG&?N_8o8DPjof(zjokSJjobwUjogI(C#E?&^i+|Xm=4bXm=AdXxRh}+C2mf+Pwq~+I<8K+WiC#+5-f=+#V$8<<^Cu zk$Z@skxK{~xvm6_+`|NoTsMM7?h%4U?oon9?lFQ!?s0-f?g@fM?n#11?kR#s?rDNX z?iqqc?pcCHE{C9z>rT+f^&n{EdJ;5py$Bk)-UN+YAA&}%FF_;MkATP>uHUPhyj?Jr zDpl#oZo|xni?}CEi2(#niGc)7i9rNSiNOR-i6I0{iJ=5diCluF#4v)U#BhS9#0Y|> z#7Kgs#3+KM#At%1#2A96#8`r+#5jVc#CU?H!~}w-#6*HkiFEcPfVPd3xlprV3c+TB z`8flx^fWHCS8O_ay0>s97iu785o{pR*|WXUbGcAY`8OF#n z>V1L@Re|ge2-;SDNU-rLko^(CUNh!Twi7gH+Xx!8?F0>4K0$-_2|L3eM!*B?IdXAb`dmky9pY(Jp_&1UV=t$A3-CxpP-TZilCAE znxK*UhMx5;Pl%5%fAPPOw2sXCDR7 z?H$d98lGba8lI8_4bQOz4Nob8hNm<^!&8Q!;VDb7;Ynwg18AMfbD>720zo5Fk)V;O zM9|1oCTL`;5HvDX2{tn6>}mk5LUk_GpwuAfby}04*XeNtjm+@`jZ7_qM&<;9UQV?M zdO4j)(97v0g2wA)g2w9$xWl?hORJB5x$<71^Gk5x$9_5x$wA5x#|>5x$k6S7Zl*UXdLMdPUww&@1wG zf?kn#5cG=dM9^%wlc3qqnV{Kl7eTY(Zh~e*HbJxD9)ezx_Y(AqypNzMaX&#*;sJuD z#DfG)i7o`aEFU6ht*Nvc&dxW5odz7G&dyJrwdz_$=dxD^mdy=4$ zdy1fudzzq;dxoHqdzPS)%OPmwx)U^VJqQ}Po&=3tFM>v{H$fxUhoF(`OVG&mBWUFM z6Et!I2pYM81dZGvf<|sIK_fSWpphF&(8%QyG;+fT8oA*Fjob)=Ms6fQBR7hmksD3W z$c-Ur_ja%%`0xwQn1+&Tgxceu{4LGpgVRO+(Irqm!5 z?QdT0F#q{4ZKm+m@SlpCmocfkQk}AiEH`-q^C z`hPYD{i&j=d1{}D8DpA$54I|v%NF9;gBF9{mCodk{C zE`mmGH$fw}hoF($OVG&eBWUFI6Et#P5j1jN6Et$)5Hxb%5;St(5j1k&6Et!M2pYK` z2pYK`2^zVd2pYMc2^zVB1dZG;1dZIU1dZHp1dZJ91dZGu1dZIE1dZHZ1dZI^1dZH3 z1dZIk1dUwkBXcj&#-w?ok;@`zXyn=uG;)^_G;)^{G;(bT8o4V78o71^jog(4joeiPjoj4)jodW^ zjoh^ajoftvjokGFjob|cjogg{ja++zM(!qpM($>UM(!4ZM($REMy>-vBiE6jk-Lqc zk-MFsk-LMSk?Taz$lXcM$aN-Yf~Lesf~LeMf~Lf1f~Le6f~Le+f~Lecf~LfHf~Ld-f~Leof?mv%2pYM`1dZGj zf<`WnpplzO(8x_AXym37G;%W>*dL^O311GF8PIF1%?Igcf%JWVIc{-czEgvH*13fK z0`mw1^jGf)0|gck%y)6HjfDjBB^j-0{#CpP5fenOl0vieA1vU{T2y7-y6nKs> zN#J=02l1%r%YOJJgBP5+;JrxD;Jrl9;Jr-H;Jrf7;Jr%F;JrrB;Jr@JQ}PBugZCyu zgZCCegZDN;gZBJO@FhVn=bZ%2h+PED zh}{Iuh&=?Y>0W|n#6AKt;&44+>xvbf`wqbf`|ybf`hlbf`(t znjS~cbU2=%=}?QH$8rKe)1fv&5AH;Qro%}DO^1^SnhvKBG#ySQXgZum(DQLRLC@G3 z1g+_rgfWsIXAv|%>JT(P>Jl_A^$40D^$D6E4G5Ya4GDT7HzH`{8WS{fO$ZvfrUZ@L z*#wPTGlE90IYA?L4nfbyxde?|3xY&`?}W&;z`Npl9t`f}Ypw2%2Zt6Ex3mAZVW5NYFfMPtcm)M9@6DnV@-g3qg=uFTvb{9cwdN*N=BwjW_6Ym~^ zCf>aSjmv!mO}zUFns^Tow1s$(pe;lff=2Ekf<`VOXym#QG;$9UG;-Yt8o5UZ8o5Ua zdOjW_XyhIzXyl$CXyl$GXyl$EXyl$IXyl$DXyl$HXykGT8oBO-F%qbr1PxR#f(EKL zK?Bu?pn>X3&_MMgXrTHN^g#ULC@G=g4SaQK@V;yK@UHdpy@D-py@E2 zpy@Dzpy@D@pfw#u&~zA0&~z9>&|?`(&~z9_(1ROK&~%tU&~%tc&~%tY&~%tg&~%tW z(DRW;&@(obpf#OF7$f;HouK(KgP{2_lb~^#MbP}1P0;+9L(u$~OVA5>9zi2FpP-Rj zK+wo7BxvLo5j1j(2^zU21dZHMf}W3M1dZHs0wQ;~&aY|mx#|3Fta_x+K38y0ni4As zni8uBni8uCni6XWni6XXniA^>niA^?Vm~CzKcXfKWl;LqbJ?j|i0nJ|^9fqhPY7Dm zPYGJn&j?!6{}HsNpA)pEI|y3SF9=%GF9}-Hodm7vE`ru{H$iK611k@5wxb?6SSrW2wKx02wKx030l*i2wKyh30l*G1g+^W z1g+_>1g+_B1g+`s1g+^G1g+_x1g+^`1g+`c1g+^m1g+`61Y1+{XHT}7tE&;NjCA&( ze0Qa#vnOC$JbMZQY=Hm4P ztZYF4k+(OU{xs{*^(Se}x??}-2-$u~*-%il@-$Bs$ zcOq!~?<8!JqwP%4gt&|Fl9afcpwZ1HOp_A#5M~P8OL$e_K0=P<(EWthQ6ioF06-hL zp_tajHP`dh7a2bT(^|fc=jVFf((_9_Z|iwG&vU=bY=1GPb^FhG{)gxPdY;;uDZgb` zW;=giTDP-fZ^rjxT794A-+2D5=ihsN!1I4SPwmTW|7A?;_Fwhq_?f+eG2$mn3&n_^EG--(ezLSkjQGjYqA}tpON+&b zpDZmNBYv{jKO0ywMtmaR*ckC! zsHI}WZ=sfs5x<36CPw@gYS|d^Td3t?#BZUNj}gCxS|LXK7HY*9@mr{sV#IHuR*n(B zg<2&>{1$4}81Y-E)nddS46h#H%p8L?V#HrFs2L;P?maHXo~Btk9;i*xWti4Ws-5TU zJ-^lSj-EgCO=deQF|FIl`ZnX|V%oa7G`};`xHOYolVhfKS%hH%X~IZ>0)#Pubap|2 zZm$p*&XdxG3F8Hd5Jm|UB}@@0Mwl;9oG?@1D8ej(5`@_TM-%1<97C8OP?9iJ;8?NB@LZAX+v_M6|Sb<7}$pV!Lc>+}k(*&wIIEYcs zcCFnCx z!jrhrW`X&u@!rByxKI<|RDveJX#`Dx(+QdYXAm?2&Ln68oJG(Cs6)^Ms7ufUs7J5~ zkj}0T(6iHk3pMr)2^#xG1dV-Tf{lGTy9q$I*OUuw*wfi(dkdR!p^bPtyScaUd@j_m zT;MHi#f5t2TYC#H;X-@n)7h7L3)^s^J?ZJ}%e;k`bD^H^wgjB-!}V|Vl6|Jc$RqCG zuHc?*Ak*3Hyn0>9g&M=F2pYqyz0%ikp^c&WEkAGJ^<1dYy1`p`BNuA4+ItIc;zEtq z&ECRWxX@mh>Fis*g&nwXuUteOy@j`NpI?0D4FRxlo(s zK?F^M!30f%Ap}i>p#)8XT!N;-FoLGRaDw*BBM90rk0fX=j3Q_*j3#IhJeHvS z@;HJf#CU=x!~}v~_Y(nnvZivOMsylMBRZX+BdrNB}egE@AB^v-X3hC?vnARJEA3aYU$oT1) z*79{cAL)6X=ZigG>iKh?KkxY#&$oL13*Fo|{9x*UN9IqY>zrw!hF%pd}GWP%gi^yeDTbD)6>o1^C-;Q^Js2!-Lm9- zd6%dZ;R1ougbM}A5H1oZOSo8|9HEs!c|vP}3WQ4pDiSUgs6=QZP?>ORRYHmt`<0+aE(AM!nFb?5UvxbO}Jj*M8XXMClPKGIGNC1 z;1t450;dAn;7L*u^RhE%WY?WJ_{(vpIZyOkC#Mtidl+XBvgP*YOb7YtN1reWau((_ zLF%|=?)Ura67*{>^$5Dd`UL%&O9O&_&7~pXI#YL3=|%+onoDDXe$Ax`LBHnGl%QX8 zIh)W?HrkAEyFhb7CxLSaodwP%+%3?8pkH%oNzkvkoJY{pdp<$G=5hf+zvglwLBHm5 z5#bTp?ZpKBnoBE!e$AydLBHm52|>T+a;by-^xcn`bKJ(wC$~x1FLRKe?uwt#xZKSr z_2m(ATL<|Cx^%%Nt{|izy~89*JIrgM+>=>${~f06z1}>wexEm=n04>>*3Hc<>vWG< zHq0$MTyNAMIcR#L&ef0XSztPQ5q6?o#U8gqQ_<9q?!qc;g0Zvv`kmeJHzu<}?y-Dl z4?O)#J5CDWtfsRI0PJQ>>&k^)<)SU+pqnY2+Px5ez_D~_LkAfv6WGq4_#R7HXGv$- zSUCsw3lzI&;LkahcV>66Z#JGJS8$fREh|=ZU<>V9hbPOG0^2h-&sgQa_V&Vakt)uT zJjtS}4&;#fJfyse>zKb(hH2Y8r?WeIp86@{+kSETNN0bFX)XV)=LbCh+w;_~nevZf zT3gd6J-LZJytU`;Ja6xLN6-6tp6mG{&zE?<%=6Um znfhGwN2Wd3F4x=PCbvVAESo)l#q)PO z|J3sxp6~bkfakw^p87L$Vn$$E131$2H|Yhk4`8~DxvCTWkkSu?uZ^0N|B>*8z)uAI zkkZeDa?%zZBn6PASR}CBIq0{GhSnV&PqEN? z;ICB|ca|)Y9USFA`ih?Tyh@3{b`Hm1pFWxta zWO#SBG)?v}5`P}M3=t=3=Wq-w$_BQ(H{OFU=PdcntZksYgKo05Q5bDjAYyB~`=Gh6 z7?~Zm1-5HFMp~5v+udh@u`14zH{|%MI*`52?K{Pks^&1smTRZF1363mdYVEt0^2zP zE7lBbcRvLGxX}9JPt70iEcsmyzLo>oL4Tay69U^g5`(PT&XPB!{wETa2%JP%DsVF4 zErC-U$i4>kHrqWlGF0+3XURBO@pK2W;$ZyM`!k#+%cRhm4zv(P<7Y*NVNjjOaCO#= z3~#MBph;THK^Pkn%}!H$yJI}tD6ri_F}iFV8Di9gCL3IcdxNG#Y;gBbe4^v*$nbH9 zW})TcJT(u^UVP_-mWy`g+|Y($SlJ>nd=RB&Xv3!+u1jo`{ACWKNsS}B`!jz~INaF? zj6BZ|ZDg*o3ql)(@$`j};Y3~(+UQ=!S~*MJmK&h{mdAxAC^Z4BDYHla-% zZR|2<$qL!QoV^>6mkF2zFmQ0h4UFkqJHXU!BUlrPvA;zwb3>&*9 zv^?DHT^rhDv~$;mmWP{y>mx({-4NO|TnsnTY(h^Rfd;WX(I)hCZ1ASY(8$~z+6*N0 zEzXi(%@z&Z>Ojtb{jgSt&}O28>`0TEj7K`(7U2YZgz9#pt=?=L#~q=~nP99_Wav5W z3~lyUW1T~rJImNzf$f~I$c*&vhS_mRf$Z#H`R)a{9`A_^pZK|tCPy_J1@9-?16Yh6 z;ep6du?L+cqa~ZVIFNI>7P4=(=YbHe50~*7wp)KiS4(VZ-9#S{AJn14G*CP(> z-gZvL*yho|b}z*+<}qiTw2dYhQ=W$GJIsKduXet8tV}mlCo!DJM-|TV|xX*do?Ik4|rN1oP_~I9PrMmIFNyX?Oun@Y*1v#lEIOosUG4i8EQ6YV5kEb z&&(S#++eQ5B$4P2b0DRr4MN*8!r?*GZC^j+%1CFD1{-i-qXXMH9nW9J(CooZAAk;T zY>2b^Vk|I@XhXgcTO1!5LOvm|ozu~|Obl%ICTwp~Xq%CglOsd(KP57ZOY;KT(*s95 zHL#sCFlwF_8OFKOoh2*f%+GM3=X3&w(ld!T+CBCG=B&_qqdA%F%%1x_y$2ba<1E=? zRzBPypmCBaluAuK;z7V%?&)6>KC|n24s6)>^u|e;?<`p*2fV<66zYR6W?^J#4HiX) z3vh90eR0FG#96XhcCeH%SYVk0*<(KpOqNH6USmaQ{U;e)8QK73{Ho9fB4VpU8;pId zp~-&-67VU$;_Dn?2`C(||(SUpu*uEYMjeQ*0uA!*#HfKq$ zRCqgKt3bX3IhPaB6MhofBwS#hMusu`XEZtILy?OABjTLzT!?e|d0=~{ph?&f+GJ$i z7oknXY5LMxvQBEe(}A>=(}&^UcM-AIJ<~9z-W^&VZs7KWHUn4fUT4YYvWI;Rn_?cbp-$FBbgZ7EL7zoD(bU0>>V z|M}jY6=)S z4Z6Hyp{+&JTRgCxD=^GE%31QB)VPELsqy;0hZ_nsNz!I0P`u`mJ`XsWJCj3PKg?9= z7@|GI)fhXK3~eJ0=UAGoyb?Hi<@!VSthW(IoL~CXUY3=GRiryCu468 zF1hl7?OcWAs1Vp*`(9(k$k1a~iVPiC<(Hy87TDe)IB};3wsRdWxHALW zI}EoKXGMm7vyQXmLpd*X9mshZjwsX%Z3IRV^#j|v0jbjYX<33Btwuz9 z21cT>X-t!X%h1;~ahQyi>NRzs_XT~>FrQ7dn;VN*HlxYrHldqn9wScGIU&yM-pk;* zL~L^BW;D4i0^5~?>}csM`A8~wo&%}i%${h4&W{mY%moql!uaIE7_s_AM0nM(*e1TJ+T(X~&@+61<%J6ey+LhFG>;BuNhqcd|cWNk~dRhx?D=n7}a zaM^1+!Zv{`9oTB@>WQoRDw=G5EFNiG?J(ISYhL3(Pw)8grqs0&PC(LL=P;Qot6uLw zj(s-ni*E>QS1)ueH#$o;%ZlwC$cl53{x?N7)Y#25If9u;np+~AH`}yU9e~st-Ty)M9?%TXhsBGYaZ!{;9>5~Mtd->`EE3+)yD&&h9-3xjjVWP|$#T|-gn9)a!afi|*dXuWzG>lN6pTy&Ma z1KZaNC#FweyXT=l?n{$ePsYX3FGiH?AL8^OxW5|^Bf8Lm4wHY(Mhy&dVB3p*{m`Eb z4s8>f@F9Wi8itE}XkhyWV8ECg84h?@WN3?r2ex|vx{eWn?XwSRMn;Bmqawp-Zggk^ z(IAhZ$q8J5Hg2rLWTK?jI0teD2BCK2LmQ05nh@F$+_+8*Z4k;$3T+5#H#xMSh~yM! z$qQ1EJO@&d!3g5i(Ck~A(;~wosp*j+)-z~w_;xfiGs2Y!y*FSixrk_MGYU~!92rJUOK7t4 zCRA~0h_id5_gF@hE#ml=2exk<24^drCBsZX11lX!oW>!Tt0Kd_F0+Jg)_|Yb+YP*8|%( z9eu_df$bcQ2OMwGBB}%*UavTz!~F1x@NCh0Va=)0#&z zFu2U@Y#Vnbr*#8{qT55v%R^U^Pqc@$dZ4jSLR*Da`P0x=qci@DX7`de9og_dqTS$n z+@pT(EcsMU_YMbgcIS-2_5MYSXu!WDVsra@U~@Y|>xmX*S7^P^)$OL)`pub$Z(3TYqY}aCRGR0_8!3DVLiaSjHCx>>F1KZ2&Uz%eIl?ZJ)#yCd@wre@|a7<*l zMrcQ;&!n55O*|mGM;8F{vcqB$(OG0ZgO(;d+F+Nm0`yTy&hdT|CAV z%}M>3zC5c$;RC75vkDcqe~P&6DL3U8e)#b&*$>>6ARY+*X#!$ z=+GhG+zCINU!d#5Niq-x(&md)$#rFCD0(V6)||lPIK0%y%guPX6)(5r5rFzco~70$#|KGmv`{87cXDq<$QArlGb>+2QOXl z(ibm_@v;Ih>+!M?FPri5JYHVJ%gcCq6)&&jQ><8uTM+5Cg1MHgu_Bnv<^KF>(3q9Pe%YAp|7wh(L*9MI` z-S+Tpk{bAn)5!otv}p7DAMDiazB|uNN}33p6P@Z>sjT@lbb+j_tY3?!vQFHZs=Xt1 z!hyo6k`;1}I#9TCR#xHMg1Og@ZJ1Xk?~c5R<66wQXi2RlUDsT&t<<@fN zZ9VqWnvs5Vh!R|E=)fS^6(EGA-YK*Ta9;!++Pqf7io*z36{KhyTV)dOPr6r}$r|_+O`Z zWSwGL+<^aox`0sf2lI5~=+fH@7Vp`5d*Kp2@BX1cR-uw#*r`J4LM4AFoGNm((IN%R zbWxcuR?tirm+7MlnduTTeRN?neT+<(EMlgQmFZGN&2(v*E>p}*mrWHprf2pK<%$cjbyrU6*JvLrkhqZ(`U6TeCb6!?WGk3nuU66I0 znY&QuF3LLI%w4Q=t+HyFxz;*&N!AHw?oyp=lU3WyU8ZxFXPs!~+UndDStpsfb~<-u z*2!k>DxJGJ>l8D0jm}-0b*h;&Pw8xkuFpEn%-x`KH)fq~=GyDrO<8A{xtn$FmaH?) z+^ss-A?qwN*HPzg%c^7MZr8axvg(>S^9a!H;m)jjX0Ef&-IZ0}%-yYX*;x(D+&wyX zZ&pJycc0GPpVi3BJ)m=@7m^t%+($@3otaHuWGdlNdRtq!NGOh7Tx6CrL=11df(F@Y&Vb=UY zoSnTWeLiN*&%xQ*R_O~cYkvF9&R&wf5VPio-t26f^hKC8zv^aZFHc{LS@RQacJ_*N zE6ldj*(=knF>8LD%`Uw Tuple[bytes, int]: + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return b"", 0 + + return encode(data), len(data) + + def decode(self, data: bytes, errors: str = "strict") -> Tuple[str, int]: + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return "", 0 + + return decode(data), len(data) + + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]: + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return b"", 0 + + labels = _unicode_dots_re.split(data) + trailing_dot = b"" + if labels: + if not labels[-1]: + trailing_dot = b"." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = b"." + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result_bytes = b".".join(result) + trailing_dot + size += len(trailing_dot) + return result_bytes, size + + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]: + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return ("", 0) + + if not isinstance(data, str): + data = str(data, "ascii") + + labels = _unicode_dots_re.split(data) + trailing_dot = "" + if labels: + if not labels[-1]: + trailing_dot = "." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = "." + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result_str = ".".join(result) + trailing_dot + size += len(trailing_dot) + return (result_str, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +def search_function(name: str) -> Optional[codecs.CodecInfo]: + if name != "idna2008": + return None + return codecs.CodecInfo( + name=name, + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) + + +codecs.register(search_function) diff --git a/.venv/Lib/site-packages/idna/compat.py b/.venv/Lib/site-packages/idna/compat.py new file mode 100644 index 0000000..1df9f2a --- /dev/null +++ b/.venv/Lib/site-packages/idna/compat.py @@ -0,0 +1,15 @@ +from typing import Any, Union + +from .core import decode, encode + + +def ToASCII(label: str) -> bytes: + return encode(label) + + +def ToUnicode(label: Union[bytes, bytearray]) -> str: + return decode(label) + + +def nameprep(s: Any) -> None: + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") diff --git a/.venv/Lib/site-packages/idna/core.py b/.venv/Lib/site-packages/idna/core.py new file mode 100644 index 0000000..9115f12 --- /dev/null +++ b/.venv/Lib/site-packages/idna/core.py @@ -0,0 +1,437 @@ +import bisect +import re +import unicodedata +from typing import Optional, Union + +from . import idnadata +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b"xn--" +_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]") + + +class IDNAError(UnicodeError): + """Base exception for all IDNA-encoding related problems""" + + pass + + +class IDNABidiError(IDNAError): + """Exception when bidirectional requirements are not satisfied""" + + pass + + +class InvalidCodepoint(IDNAError): + """Exception when a disallowed or unallocated codepoint is used""" + + pass + + +class InvalidCodepointContext(IDNAError): + """Exception when the codepoint is not valid in the context it is used""" + + pass + + +def _combining_class(cp: int) -> int: + v = unicodedata.combining(chr(cp)) + if v == 0: + if not unicodedata.name(chr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + + +def _is_script(cp: str, script: str) -> bool: + return intranges_contain(ord(cp), idnadata.scripts[script]) + + +def _punycode(s: str) -> bytes: + return s.encode("punycode") + + +def _unot(s: int) -> str: + return "U+{:04X}".format(s) + + +def valid_label_length(label: Union[bytes, str]) -> bool: + if len(label) > 63: + return False + return True + + +def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool: + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label: str, check_ltr: bool = False) -> bool: + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == "": + # String likely comes from a newer version of Unicode + raise IDNABidiError("Unknown directionality in label {} at position {}".format(repr(label), idx)) + if direction in ["R", "AL", "AN"]: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ["R", "AL"]: + rtl = True + elif direction == "L": + rtl = False + else: + raise IDNABidiError("First codepoint in label {} must be directionality L, R or AL".format(repr(label))) + + valid_ending = False + number_type: Optional[str] = None + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if direction not in [ + "R", + "AL", + "AN", + "EN", + "ES", + "CS", + "ET", + "ON", + "BN", + "NSM", + ]: + raise IDNABidiError("Invalid direction for codepoint at position {} in a right-to-left label".format(idx)) + # Bidi rule 3 + if direction in ["R", "AL", "EN", "AN"]: + valid_ending = True + elif direction != "NSM": + valid_ending = False + # Bidi rule 4 + if direction in ["AN", "EN"]: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError("Can not mix numeral types in a right-to-left label") + else: + # Bidi rule 5 + if direction not in ["L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"]: + raise IDNABidiError("Invalid direction for codepoint at position {} in a left-to-right label".format(idx)) + # Bidi rule 6 + if direction in ["L", "EN"]: + valid_ending = True + elif direction != "NSM": + valid_ending = False + + if not valid_ending: + raise IDNABidiError("Label ends with illegal codepoint directionality") + + return True + + +def check_initial_combiner(label: str) -> bool: + if unicodedata.category(label[0])[0] == "M": + raise IDNAError("Label begins with an illegal combining character") + return True + + +def check_hyphen_ok(label: str) -> bool: + if label[2:4] == "--": + raise IDNAError("Label has disallowed hyphens in 3rd and 4th position") + if label[0] == "-" or label[-1] == "-": + raise IDNAError("Label must not start or end with a hyphen") + return True + + +def check_nfc(label: str) -> None: + if unicodedata.normalize("NFC", label) != label: + raise IDNAError("Label must be in Normalization Form C") + + +def valid_contextj(label: str, pos: int) -> bool: + cp_value = ord(label[pos]) + + if cp_value == 0x200C: + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos - 1, -1, -1): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord("T"): + continue + elif joining_type in [ord("L"), ord("D")]: + ok = True + break + else: + break + + if not ok: + return False + + ok = False + for i in range(pos + 1, len(label)): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord("T"): + continue + elif joining_type in [ord("R"), ord("D")]: + ok = True + break + else: + break + return ok + + if cp_value == 0x200D: + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + return False + + +def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: + cp_value = ord(label[pos]) + + if cp_value == 0x00B7: + if 0 < pos < len(label) - 1: + if ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label) - 1 and len(label) > 1: + return _is_script(label[pos + 1], "Greek") + return False + + elif cp_value == 0x05F3 or cp_value == 0x05F4: + if pos > 0: + return _is_script(label[pos - 1], "Hebrew") + return False + + elif cp_value == 0x30FB: + for cp in label: + if cp == "\u30fb": + continue + if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6F0 <= ord(cp) <= 0x06F9: + return False + return True + + elif 0x6F0 <= cp_value <= 0x6F9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + return False + + +def check_label(label: Union[str, bytes, bytearray]) -> None: + if isinstance(label, (bytes, bytearray)): + label = label.decode("utf-8") + if len(label) == 0: + raise IDNAError("Empty Label") + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for pos, cp in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext( + "Joiner {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label)) + ) + except ValueError: + raise IDNAError( + "Unknown codepoint adjacent to joiner {} at position {} in {}".format( + _unot(cp_value), pos + 1, repr(label) + ) + ) + elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]): + if not valid_contexto(label, pos): + raise InvalidCodepointContext( + "Codepoint {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label)) + ) + else: + raise InvalidCodepoint( + "Codepoint {} at position {} of {} not allowed".format(_unot(cp_value), pos + 1, repr(label)) + ) + + check_bidi(label) + + +def alabel(label: str) -> bytes: + try: + label_bytes = label.encode("ascii") + ulabel(label_bytes) + if not valid_label_length(label_bytes): + raise IDNAError("Label too long") + return label_bytes + except UnicodeEncodeError: + pass + + check_label(label) + label_bytes = _alabel_prefix + _punycode(label) + + if not valid_label_length(label_bytes): + raise IDNAError("Label too long") + + return label_bytes + + +def ulabel(label: Union[str, bytes, bytearray]) -> str: + if not isinstance(label, (bytes, bytearray)): + try: + label_bytes = label.encode("ascii") + except UnicodeEncodeError: + check_label(label) + return label + else: + label_bytes = label + + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix) :] + if not label_bytes: + raise IDNAError("Malformed A-label, no Punycode eligible content found") + if label_bytes.decode("ascii")[-1] == "-": + raise IDNAError("A-label must not end with a hyphen") + else: + check_label(label_bytes) + return label_bytes.decode("ascii") + + try: + label = label_bytes.decode("punycode") + except UnicodeError: + raise IDNAError("Invalid A-label") + check_label(label) + return label + + +def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: + """Re-map the characters in the string according to UTS46 processing.""" + from .uts46data import uts46data + + output = "" + + for pos, char in enumerate(domain): + code_point = ord(char) + try: + uts46row = uts46data[code_point if code_point < 256 else bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + status = uts46row[1] + replacement: Optional[str] = None + if len(uts46row) == 3: + replacement = uts46row[2] + if ( + status == "V" + or (status == "D" and not transitional) + or (status == "3" and not std3_rules and replacement is None) + ): + output += char + elif replacement is not None and ( + status == "M" or (status == "3" and not std3_rules) or (status == "D" and transitional) + ): + output += replacement + elif status != "I": + raise IndexError() + except IndexError: + raise InvalidCodepoint( + "Codepoint {} not allowed at position {} in {}".format(_unot(code_point), pos + 1, repr(domain)) + ) + + return unicodedata.normalize("NFC", output) + + +def encode( + s: Union[str, bytes, bytearray], + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, + transitional: bool = False, +) -> bytes: + if not isinstance(s, str): + try: + s = str(s, "ascii") + except UnicodeDecodeError: + raise IDNAError("should pass a unicode string to the function rather than a byte string.") + if uts46: + s = uts46_remap(s, std3_rules, transitional) + trailing_dot = False + result = [] + if strict: + labels = s.split(".") + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == [""]: + raise IDNAError("Empty domain") + if labels[-1] == "": + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label") + if trailing_dot: + result.append(b"") + s = b".".join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError("Domain too long") + return s + + +def decode( + s: Union[str, bytes, bytearray], + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, +) -> str: + try: + if not isinstance(s, str): + s = str(s, "ascii") + except UnicodeDecodeError: + raise IDNAError("Invalid ASCII in A-label") + if uts46: + s = uts46_remap(s, std3_rules, False) + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split(".") + if not labels or labels == [""]: + raise IDNAError("Empty domain") + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label") + if trailing_dot: + result.append("") + return ".".join(result) diff --git a/.venv/Lib/site-packages/idna/idnadata.py b/.venv/Lib/site-packages/idna/idnadata.py new file mode 100644 index 0000000..4be6004 --- /dev/null +++ b/.venv/Lib/site-packages/idna/idnadata.py @@ -0,0 +1,4243 @@ +# This file is automatically generated by tools/idna-data + +__version__ = "15.1.0" +scripts = { + "Greek": ( + 0x37000000374, + 0x37500000378, + 0x37A0000037E, + 0x37F00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038B, + 0x38C0000038D, + 0x38E000003A2, + 0x3A3000003E2, + 0x3F000000400, + 0x1D2600001D2B, + 0x1D5D00001D62, + 0x1D6600001D6B, + 0x1DBF00001DC0, + 0x1F0000001F16, + 0x1F1800001F1E, + 0x1F2000001F46, + 0x1F4800001F4E, + 0x1F5000001F58, + 0x1F5900001F5A, + 0x1F5B00001F5C, + 0x1F5D00001F5E, + 0x1F5F00001F7E, + 0x1F8000001FB5, + 0x1FB600001FC5, + 0x1FC600001FD4, + 0x1FD600001FDC, + 0x1FDD00001FF0, + 0x1FF200001FF5, + 0x1FF600001FFF, + 0x212600002127, + 0xAB650000AB66, + 0x101400001018F, + 0x101A0000101A1, + 0x1D2000001D246, + ), + "Han": ( + 0x2E8000002E9A, + 0x2E9B00002EF4, + 0x2F0000002FD6, + 0x300500003006, + 0x300700003008, + 0x30210000302A, + 0x30380000303C, + 0x340000004DC0, + 0x4E000000A000, + 0xF9000000FA6E, + 0xFA700000FADA, + 0x16FE200016FE4, + 0x16FF000016FF2, + 0x200000002A6E0, + 0x2A7000002B73A, + 0x2B7400002B81E, + 0x2B8200002CEA2, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x2F8000002FA1E, + 0x300000003134B, + 0x31350000323B0, + ), + "Hebrew": ( + 0x591000005C8, + 0x5D0000005EB, + 0x5EF000005F5, + 0xFB1D0000FB37, + 0xFB380000FB3D, + 0xFB3E0000FB3F, + 0xFB400000FB42, + 0xFB430000FB45, + 0xFB460000FB50, + ), + "Hiragana": ( + 0x304100003097, + 0x309D000030A0, + 0x1B0010001B120, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1F2000001F201, + ), + "Katakana": ( + 0x30A1000030FB, + 0x30FD00003100, + 0x31F000003200, + 0x32D0000032FF, + 0x330000003358, + 0xFF660000FF70, + 0xFF710000FF9E, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B001, + 0x1B1200001B123, + 0x1B1550001B156, + 0x1B1640001B168, + ), +} +joining_types = { + 0xAD: 84, + 0x300: 84, + 0x301: 84, + 0x302: 84, + 0x303: 84, + 0x304: 84, + 0x305: 84, + 0x306: 84, + 0x307: 84, + 0x308: 84, + 0x309: 84, + 0x30A: 84, + 0x30B: 84, + 0x30C: 84, + 0x30D: 84, + 0x30E: 84, + 0x30F: 84, + 0x310: 84, + 0x311: 84, + 0x312: 84, + 0x313: 84, + 0x314: 84, + 0x315: 84, + 0x316: 84, + 0x317: 84, + 0x318: 84, + 0x319: 84, + 0x31A: 84, + 0x31B: 84, + 0x31C: 84, + 0x31D: 84, + 0x31E: 84, + 0x31F: 84, + 0x320: 84, + 0x321: 84, + 0x322: 84, + 0x323: 84, + 0x324: 84, + 0x325: 84, + 0x326: 84, + 0x327: 84, + 0x328: 84, + 0x329: 84, + 0x32A: 84, + 0x32B: 84, + 0x32C: 84, + 0x32D: 84, + 0x32E: 84, + 0x32F: 84, + 0x330: 84, + 0x331: 84, + 0x332: 84, + 0x333: 84, + 0x334: 84, + 0x335: 84, + 0x336: 84, + 0x337: 84, + 0x338: 84, + 0x339: 84, + 0x33A: 84, + 0x33B: 84, + 0x33C: 84, + 0x33D: 84, + 0x33E: 84, + 0x33F: 84, + 0x340: 84, + 0x341: 84, + 0x342: 84, + 0x343: 84, + 0x344: 84, + 0x345: 84, + 0x346: 84, + 0x347: 84, + 0x348: 84, + 0x349: 84, + 0x34A: 84, + 0x34B: 84, + 0x34C: 84, + 0x34D: 84, + 0x34E: 84, + 0x34F: 84, + 0x350: 84, + 0x351: 84, + 0x352: 84, + 0x353: 84, + 0x354: 84, + 0x355: 84, + 0x356: 84, + 0x357: 84, + 0x358: 84, + 0x359: 84, + 0x35A: 84, + 0x35B: 84, + 0x35C: 84, + 0x35D: 84, + 0x35E: 84, + 0x35F: 84, + 0x360: 84, + 0x361: 84, + 0x362: 84, + 0x363: 84, + 0x364: 84, + 0x365: 84, + 0x366: 84, + 0x367: 84, + 0x368: 84, + 0x369: 84, + 0x36A: 84, + 0x36B: 84, + 0x36C: 84, + 0x36D: 84, + 0x36E: 84, + 0x36F: 84, + 0x483: 84, + 0x484: 84, + 0x485: 84, + 0x486: 84, + 0x487: 84, + 0x488: 84, + 0x489: 84, + 0x591: 84, + 0x592: 84, + 0x593: 84, + 0x594: 84, + 0x595: 84, + 0x596: 84, + 0x597: 84, + 0x598: 84, + 0x599: 84, + 0x59A: 84, + 0x59B: 84, + 0x59C: 84, + 0x59D: 84, + 0x59E: 84, + 0x59F: 84, + 0x5A0: 84, + 0x5A1: 84, + 0x5A2: 84, + 0x5A3: 84, + 0x5A4: 84, + 0x5A5: 84, + 0x5A6: 84, + 0x5A7: 84, + 0x5A8: 84, + 0x5A9: 84, + 0x5AA: 84, + 0x5AB: 84, + 0x5AC: 84, + 0x5AD: 84, + 0x5AE: 84, + 0x5AF: 84, + 0x5B0: 84, + 0x5B1: 84, + 0x5B2: 84, + 0x5B3: 84, + 0x5B4: 84, + 0x5B5: 84, + 0x5B6: 84, + 0x5B7: 84, + 0x5B8: 84, + 0x5B9: 84, + 0x5BA: 84, + 0x5BB: 84, + 0x5BC: 84, + 0x5BD: 84, + 0x5BF: 84, + 0x5C1: 84, + 0x5C2: 84, + 0x5C4: 84, + 0x5C5: 84, + 0x5C7: 84, + 0x610: 84, + 0x611: 84, + 0x612: 84, + 0x613: 84, + 0x614: 84, + 0x615: 84, + 0x616: 84, + 0x617: 84, + 0x618: 84, + 0x619: 84, + 0x61A: 84, + 0x61C: 84, + 0x620: 68, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62A: 68, + 0x62B: 68, + 0x62C: 68, + 0x62D: 68, + 0x62E: 68, + 0x62F: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63A: 68, + 0x63B: 68, + 0x63C: 68, + 0x63D: 68, + 0x63E: 68, + 0x63F: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64A: 68, + 0x64B: 84, + 0x64C: 84, + 0x64D: 84, + 0x64E: 84, + 0x64F: 84, + 0x650: 84, + 0x651: 84, + 0x652: 84, + 0x653: 84, + 0x654: 84, + 0x655: 84, + 0x656: 84, + 0x657: 84, + 0x658: 84, + 0x659: 84, + 0x65A: 84, + 0x65B: 84, + 0x65C: 84, + 0x65D: 84, + 0x65E: 84, + 0x65F: 84, + 0x66E: 68, + 0x66F: 68, + 0x670: 84, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67A: 68, + 0x67B: 68, + 0x67C: 68, + 0x67D: 68, + 0x67E: 68, + 0x67F: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68A: 82, + 0x68B: 82, + 0x68C: 82, + 0x68D: 82, + 0x68E: 82, + 0x68F: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69A: 68, + 0x69B: 68, + 0x69C: 68, + 0x69D: 68, + 0x69E: 68, + 0x69F: 68, + 0x6A0: 68, + 0x6A1: 68, + 0x6A2: 68, + 0x6A3: 68, + 0x6A4: 68, + 0x6A5: 68, + 0x6A6: 68, + 0x6A7: 68, + 0x6A8: 68, + 0x6A9: 68, + 0x6AA: 68, + 0x6AB: 68, + 0x6AC: 68, + 0x6AD: 68, + 0x6AE: 68, + 0x6AF: 68, + 0x6B0: 68, + 0x6B1: 68, + 0x6B2: 68, + 0x6B3: 68, + 0x6B4: 68, + 0x6B5: 68, + 0x6B6: 68, + 0x6B7: 68, + 0x6B8: 68, + 0x6B9: 68, + 0x6BA: 68, + 0x6BB: 68, + 0x6BC: 68, + 0x6BD: 68, + 0x6BE: 68, + 0x6BF: 68, + 0x6C0: 82, + 0x6C1: 68, + 0x6C2: 68, + 0x6C3: 82, + 0x6C4: 82, + 0x6C5: 82, + 0x6C6: 82, + 0x6C7: 82, + 0x6C8: 82, + 0x6C9: 82, + 0x6CA: 82, + 0x6CB: 82, + 0x6CC: 68, + 0x6CD: 82, + 0x6CE: 68, + 0x6CF: 82, + 0x6D0: 68, + 0x6D1: 68, + 0x6D2: 82, + 0x6D3: 82, + 0x6D5: 82, + 0x6D6: 84, + 0x6D7: 84, + 0x6D8: 84, + 0x6D9: 84, + 0x6DA: 84, + 0x6DB: 84, + 0x6DC: 84, + 0x6DF: 84, + 0x6E0: 84, + 0x6E1: 84, + 0x6E2: 84, + 0x6E3: 84, + 0x6E4: 84, + 0x6E7: 84, + 0x6E8: 84, + 0x6EA: 84, + 0x6EB: 84, + 0x6EC: 84, + 0x6ED: 84, + 0x6EE: 82, + 0x6EF: 82, + 0x6FA: 68, + 0x6FB: 68, + 0x6FC: 68, + 0x6FF: 68, + 0x70F: 84, + 0x710: 82, + 0x711: 84, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71A: 68, + 0x71B: 68, + 0x71C: 68, + 0x71D: 68, + 0x71E: 82, + 0x71F: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72A: 82, + 0x72B: 68, + 0x72C: 82, + 0x72D: 68, + 0x72E: 68, + 0x72F: 82, + 0x730: 84, + 0x731: 84, + 0x732: 84, + 0x733: 84, + 0x734: 84, + 0x735: 84, + 0x736: 84, + 0x737: 84, + 0x738: 84, + 0x739: 84, + 0x73A: 84, + 0x73B: 84, + 0x73C: 84, + 0x73D: 84, + 0x73E: 84, + 0x73F: 84, + 0x740: 84, + 0x741: 84, + 0x742: 84, + 0x743: 84, + 0x744: 84, + 0x745: 84, + 0x746: 84, + 0x747: 84, + 0x748: 84, + 0x749: 84, + 0x74A: 84, + 0x74D: 82, + 0x74E: 68, + 0x74F: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75A: 82, + 0x75B: 82, + 0x75C: 68, + 0x75D: 68, + 0x75E: 68, + 0x75F: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76A: 68, + 0x76B: 82, + 0x76C: 82, + 0x76D: 68, + 0x76E: 68, + 0x76F: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77A: 68, + 0x77B: 68, + 0x77C: 68, + 0x77D: 68, + 0x77E: 68, + 0x77F: 68, + 0x7A6: 84, + 0x7A7: 84, + 0x7A8: 84, + 0x7A9: 84, + 0x7AA: 84, + 0x7AB: 84, + 0x7AC: 84, + 0x7AD: 84, + 0x7AE: 84, + 0x7AF: 84, + 0x7B0: 84, + 0x7CA: 68, + 0x7CB: 68, + 0x7CC: 68, + 0x7CD: 68, + 0x7CE: 68, + 0x7CF: 68, + 0x7D0: 68, + 0x7D1: 68, + 0x7D2: 68, + 0x7D3: 68, + 0x7D4: 68, + 0x7D5: 68, + 0x7D6: 68, + 0x7D7: 68, + 0x7D8: 68, + 0x7D9: 68, + 0x7DA: 68, + 0x7DB: 68, + 0x7DC: 68, + 0x7DD: 68, + 0x7DE: 68, + 0x7DF: 68, + 0x7E0: 68, + 0x7E1: 68, + 0x7E2: 68, + 0x7E3: 68, + 0x7E4: 68, + 0x7E5: 68, + 0x7E6: 68, + 0x7E7: 68, + 0x7E8: 68, + 0x7E9: 68, + 0x7EA: 68, + 0x7EB: 84, + 0x7EC: 84, + 0x7ED: 84, + 0x7EE: 84, + 0x7EF: 84, + 0x7F0: 84, + 0x7F1: 84, + 0x7F2: 84, + 0x7F3: 84, + 0x7FA: 67, + 0x7FD: 84, + 0x816: 84, + 0x817: 84, + 0x818: 84, + 0x819: 84, + 0x81B: 84, + 0x81C: 84, + 0x81D: 84, + 0x81E: 84, + 0x81F: 84, + 0x820: 84, + 0x821: 84, + 0x822: 84, + 0x823: 84, + 0x825: 84, + 0x826: 84, + 0x827: 84, + 0x829: 84, + 0x82A: 84, + 0x82B: 84, + 0x82C: 84, + 0x82D: 84, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84A: 68, + 0x84B: 68, + 0x84C: 68, + 0x84D: 68, + 0x84E: 68, + 0x84F: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 82, + 0x857: 82, + 0x858: 82, + 0x859: 84, + 0x85A: 84, + 0x85B: 84, + 0x860: 68, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86A: 82, + 0x870: 82, + 0x871: 82, + 0x872: 82, + 0x873: 82, + 0x874: 82, + 0x875: 82, + 0x876: 82, + 0x877: 82, + 0x878: 82, + 0x879: 82, + 0x87A: 82, + 0x87B: 82, + 0x87C: 82, + 0x87D: 82, + 0x87E: 82, + 0x87F: 82, + 0x880: 82, + 0x881: 82, + 0x882: 82, + 0x883: 67, + 0x884: 67, + 0x885: 67, + 0x886: 68, + 0x889: 68, + 0x88A: 68, + 0x88B: 68, + 0x88C: 68, + 0x88D: 68, + 0x88E: 82, + 0x898: 84, + 0x899: 84, + 0x89A: 84, + 0x89B: 84, + 0x89C: 84, + 0x89D: 84, + 0x89E: 84, + 0x89F: 84, + 0x8A0: 68, + 0x8A1: 68, + 0x8A2: 68, + 0x8A3: 68, + 0x8A4: 68, + 0x8A5: 68, + 0x8A6: 68, + 0x8A7: 68, + 0x8A8: 68, + 0x8A9: 68, + 0x8AA: 82, + 0x8AB: 82, + 0x8AC: 82, + 0x8AE: 82, + 0x8AF: 68, + 0x8B0: 68, + 0x8B1: 82, + 0x8B2: 82, + 0x8B3: 68, + 0x8B4: 68, + 0x8B5: 68, + 0x8B6: 68, + 0x8B7: 68, + 0x8B8: 68, + 0x8B9: 82, + 0x8BA: 68, + 0x8BB: 68, + 0x8BC: 68, + 0x8BD: 68, + 0x8BE: 68, + 0x8BF: 68, + 0x8C0: 68, + 0x8C1: 68, + 0x8C2: 68, + 0x8C3: 68, + 0x8C4: 68, + 0x8C5: 68, + 0x8C6: 68, + 0x8C7: 68, + 0x8C8: 68, + 0x8CA: 84, + 0x8CB: 84, + 0x8CC: 84, + 0x8CD: 84, + 0x8CE: 84, + 0x8CF: 84, + 0x8D0: 84, + 0x8D1: 84, + 0x8D2: 84, + 0x8D3: 84, + 0x8D4: 84, + 0x8D5: 84, + 0x8D6: 84, + 0x8D7: 84, + 0x8D8: 84, + 0x8D9: 84, + 0x8DA: 84, + 0x8DB: 84, + 0x8DC: 84, + 0x8DD: 84, + 0x8DE: 84, + 0x8DF: 84, + 0x8E0: 84, + 0x8E1: 84, + 0x8E3: 84, + 0x8E4: 84, + 0x8E5: 84, + 0x8E6: 84, + 0x8E7: 84, + 0x8E8: 84, + 0x8E9: 84, + 0x8EA: 84, + 0x8EB: 84, + 0x8EC: 84, + 0x8ED: 84, + 0x8EE: 84, + 0x8EF: 84, + 0x8F0: 84, + 0x8F1: 84, + 0x8F2: 84, + 0x8F3: 84, + 0x8F4: 84, + 0x8F5: 84, + 0x8F6: 84, + 0x8F7: 84, + 0x8F8: 84, + 0x8F9: 84, + 0x8FA: 84, + 0x8FB: 84, + 0x8FC: 84, + 0x8FD: 84, + 0x8FE: 84, + 0x8FF: 84, + 0x900: 84, + 0x901: 84, + 0x902: 84, + 0x93A: 84, + 0x93C: 84, + 0x941: 84, + 0x942: 84, + 0x943: 84, + 0x944: 84, + 0x945: 84, + 0x946: 84, + 0x947: 84, + 0x948: 84, + 0x94D: 84, + 0x951: 84, + 0x952: 84, + 0x953: 84, + 0x954: 84, + 0x955: 84, + 0x956: 84, + 0x957: 84, + 0x962: 84, + 0x963: 84, + 0x981: 84, + 0x9BC: 84, + 0x9C1: 84, + 0x9C2: 84, + 0x9C3: 84, + 0x9C4: 84, + 0x9CD: 84, + 0x9E2: 84, + 0x9E3: 84, + 0x9FE: 84, + 0xA01: 84, + 0xA02: 84, + 0xA3C: 84, + 0xA41: 84, + 0xA42: 84, + 0xA47: 84, + 0xA48: 84, + 0xA4B: 84, + 0xA4C: 84, + 0xA4D: 84, + 0xA51: 84, + 0xA70: 84, + 0xA71: 84, + 0xA75: 84, + 0xA81: 84, + 0xA82: 84, + 0xABC: 84, + 0xAC1: 84, + 0xAC2: 84, + 0xAC3: 84, + 0xAC4: 84, + 0xAC5: 84, + 0xAC7: 84, + 0xAC8: 84, + 0xACD: 84, + 0xAE2: 84, + 0xAE3: 84, + 0xAFA: 84, + 0xAFB: 84, + 0xAFC: 84, + 0xAFD: 84, + 0xAFE: 84, + 0xAFF: 84, + 0xB01: 84, + 0xB3C: 84, + 0xB3F: 84, + 0xB41: 84, + 0xB42: 84, + 0xB43: 84, + 0xB44: 84, + 0xB4D: 84, + 0xB55: 84, + 0xB56: 84, + 0xB62: 84, + 0xB63: 84, + 0xB82: 84, + 0xBC0: 84, + 0xBCD: 84, + 0xC00: 84, + 0xC04: 84, + 0xC3C: 84, + 0xC3E: 84, + 0xC3F: 84, + 0xC40: 84, + 0xC46: 84, + 0xC47: 84, + 0xC48: 84, + 0xC4A: 84, + 0xC4B: 84, + 0xC4C: 84, + 0xC4D: 84, + 0xC55: 84, + 0xC56: 84, + 0xC62: 84, + 0xC63: 84, + 0xC81: 84, + 0xCBC: 84, + 0xCBF: 84, + 0xCC6: 84, + 0xCCC: 84, + 0xCCD: 84, + 0xCE2: 84, + 0xCE3: 84, + 0xD00: 84, + 0xD01: 84, + 0xD3B: 84, + 0xD3C: 84, + 0xD41: 84, + 0xD42: 84, + 0xD43: 84, + 0xD44: 84, + 0xD4D: 84, + 0xD62: 84, + 0xD63: 84, + 0xD81: 84, + 0xDCA: 84, + 0xDD2: 84, + 0xDD3: 84, + 0xDD4: 84, + 0xDD6: 84, + 0xE31: 84, + 0xE34: 84, + 0xE35: 84, + 0xE36: 84, + 0xE37: 84, + 0xE38: 84, + 0xE39: 84, + 0xE3A: 84, + 0xE47: 84, + 0xE48: 84, + 0xE49: 84, + 0xE4A: 84, + 0xE4B: 84, + 0xE4C: 84, + 0xE4D: 84, + 0xE4E: 84, + 0xEB1: 84, + 0xEB4: 84, + 0xEB5: 84, + 0xEB6: 84, + 0xEB7: 84, + 0xEB8: 84, + 0xEB9: 84, + 0xEBA: 84, + 0xEBB: 84, + 0xEBC: 84, + 0xEC8: 84, + 0xEC9: 84, + 0xECA: 84, + 0xECB: 84, + 0xECC: 84, + 0xECD: 84, + 0xECE: 84, + 0xF18: 84, + 0xF19: 84, + 0xF35: 84, + 0xF37: 84, + 0xF39: 84, + 0xF71: 84, + 0xF72: 84, + 0xF73: 84, + 0xF74: 84, + 0xF75: 84, + 0xF76: 84, + 0xF77: 84, + 0xF78: 84, + 0xF79: 84, + 0xF7A: 84, + 0xF7B: 84, + 0xF7C: 84, + 0xF7D: 84, + 0xF7E: 84, + 0xF80: 84, + 0xF81: 84, + 0xF82: 84, + 0xF83: 84, + 0xF84: 84, + 0xF86: 84, + 0xF87: 84, + 0xF8D: 84, + 0xF8E: 84, + 0xF8F: 84, + 0xF90: 84, + 0xF91: 84, + 0xF92: 84, + 0xF93: 84, + 0xF94: 84, + 0xF95: 84, + 0xF96: 84, + 0xF97: 84, + 0xF99: 84, + 0xF9A: 84, + 0xF9B: 84, + 0xF9C: 84, + 0xF9D: 84, + 0xF9E: 84, + 0xF9F: 84, + 0xFA0: 84, + 0xFA1: 84, + 0xFA2: 84, + 0xFA3: 84, + 0xFA4: 84, + 0xFA5: 84, + 0xFA6: 84, + 0xFA7: 84, + 0xFA8: 84, + 0xFA9: 84, + 0xFAA: 84, + 0xFAB: 84, + 0xFAC: 84, + 0xFAD: 84, + 0xFAE: 84, + 0xFAF: 84, + 0xFB0: 84, + 0xFB1: 84, + 0xFB2: 84, + 0xFB3: 84, + 0xFB4: 84, + 0xFB5: 84, + 0xFB6: 84, + 0xFB7: 84, + 0xFB8: 84, + 0xFB9: 84, + 0xFBA: 84, + 0xFBB: 84, + 0xFBC: 84, + 0xFC6: 84, + 0x102D: 84, + 0x102E: 84, + 0x102F: 84, + 0x1030: 84, + 0x1032: 84, + 0x1033: 84, + 0x1034: 84, + 0x1035: 84, + 0x1036: 84, + 0x1037: 84, + 0x1039: 84, + 0x103A: 84, + 0x103D: 84, + 0x103E: 84, + 0x1058: 84, + 0x1059: 84, + 0x105E: 84, + 0x105F: 84, + 0x1060: 84, + 0x1071: 84, + 0x1072: 84, + 0x1073: 84, + 0x1074: 84, + 0x1082: 84, + 0x1085: 84, + 0x1086: 84, + 0x108D: 84, + 0x109D: 84, + 0x135D: 84, + 0x135E: 84, + 0x135F: 84, + 0x1712: 84, + 0x1713: 84, + 0x1714: 84, + 0x1732: 84, + 0x1733: 84, + 0x1752: 84, + 0x1753: 84, + 0x1772: 84, + 0x1773: 84, + 0x17B4: 84, + 0x17B5: 84, + 0x17B7: 84, + 0x17B8: 84, + 0x17B9: 84, + 0x17BA: 84, + 0x17BB: 84, + 0x17BC: 84, + 0x17BD: 84, + 0x17C6: 84, + 0x17C9: 84, + 0x17CA: 84, + 0x17CB: 84, + 0x17CC: 84, + 0x17CD: 84, + 0x17CE: 84, + 0x17CF: 84, + 0x17D0: 84, + 0x17D1: 84, + 0x17D2: 84, + 0x17D3: 84, + 0x17DD: 84, + 0x1807: 68, + 0x180A: 67, + 0x180B: 84, + 0x180C: 84, + 0x180D: 84, + 0x180F: 84, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182A: 68, + 0x182B: 68, + 0x182C: 68, + 0x182D: 68, + 0x182E: 68, + 0x182F: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183A: 68, + 0x183B: 68, + 0x183C: 68, + 0x183D: 68, + 0x183E: 68, + 0x183F: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184A: 68, + 0x184B: 68, + 0x184C: 68, + 0x184D: 68, + 0x184E: 68, + 0x184F: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185A: 68, + 0x185B: 68, + 0x185C: 68, + 0x185D: 68, + 0x185E: 68, + 0x185F: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186A: 68, + 0x186B: 68, + 0x186C: 68, + 0x186D: 68, + 0x186E: 68, + 0x186F: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188A: 68, + 0x188B: 68, + 0x188C: 68, + 0x188D: 68, + 0x188E: 68, + 0x188F: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189A: 68, + 0x189B: 68, + 0x189C: 68, + 0x189D: 68, + 0x189E: 68, + 0x189F: 68, + 0x18A0: 68, + 0x18A1: 68, + 0x18A2: 68, + 0x18A3: 68, + 0x18A4: 68, + 0x18A5: 68, + 0x18A6: 68, + 0x18A7: 68, + 0x18A8: 68, + 0x18A9: 84, + 0x18AA: 68, + 0x1920: 84, + 0x1921: 84, + 0x1922: 84, + 0x1927: 84, + 0x1928: 84, + 0x1932: 84, + 0x1939: 84, + 0x193A: 84, + 0x193B: 84, + 0x1A17: 84, + 0x1A18: 84, + 0x1A1B: 84, + 0x1A56: 84, + 0x1A58: 84, + 0x1A59: 84, + 0x1A5A: 84, + 0x1A5B: 84, + 0x1A5C: 84, + 0x1A5D: 84, + 0x1A5E: 84, + 0x1A60: 84, + 0x1A62: 84, + 0x1A65: 84, + 0x1A66: 84, + 0x1A67: 84, + 0x1A68: 84, + 0x1A69: 84, + 0x1A6A: 84, + 0x1A6B: 84, + 0x1A6C: 84, + 0x1A73: 84, + 0x1A74: 84, + 0x1A75: 84, + 0x1A76: 84, + 0x1A77: 84, + 0x1A78: 84, + 0x1A79: 84, + 0x1A7A: 84, + 0x1A7B: 84, + 0x1A7C: 84, + 0x1A7F: 84, + 0x1AB0: 84, + 0x1AB1: 84, + 0x1AB2: 84, + 0x1AB3: 84, + 0x1AB4: 84, + 0x1AB5: 84, + 0x1AB6: 84, + 0x1AB7: 84, + 0x1AB8: 84, + 0x1AB9: 84, + 0x1ABA: 84, + 0x1ABB: 84, + 0x1ABC: 84, + 0x1ABD: 84, + 0x1ABE: 84, + 0x1ABF: 84, + 0x1AC0: 84, + 0x1AC1: 84, + 0x1AC2: 84, + 0x1AC3: 84, + 0x1AC4: 84, + 0x1AC5: 84, + 0x1AC6: 84, + 0x1AC7: 84, + 0x1AC8: 84, + 0x1AC9: 84, + 0x1ACA: 84, + 0x1ACB: 84, + 0x1ACC: 84, + 0x1ACD: 84, + 0x1ACE: 84, + 0x1B00: 84, + 0x1B01: 84, + 0x1B02: 84, + 0x1B03: 84, + 0x1B34: 84, + 0x1B36: 84, + 0x1B37: 84, + 0x1B38: 84, + 0x1B39: 84, + 0x1B3A: 84, + 0x1B3C: 84, + 0x1B42: 84, + 0x1B6B: 84, + 0x1B6C: 84, + 0x1B6D: 84, + 0x1B6E: 84, + 0x1B6F: 84, + 0x1B70: 84, + 0x1B71: 84, + 0x1B72: 84, + 0x1B73: 84, + 0x1B80: 84, + 0x1B81: 84, + 0x1BA2: 84, + 0x1BA3: 84, + 0x1BA4: 84, + 0x1BA5: 84, + 0x1BA8: 84, + 0x1BA9: 84, + 0x1BAB: 84, + 0x1BAC: 84, + 0x1BAD: 84, + 0x1BE6: 84, + 0x1BE8: 84, + 0x1BE9: 84, + 0x1BED: 84, + 0x1BEF: 84, + 0x1BF0: 84, + 0x1BF1: 84, + 0x1C2C: 84, + 0x1C2D: 84, + 0x1C2E: 84, + 0x1C2F: 84, + 0x1C30: 84, + 0x1C31: 84, + 0x1C32: 84, + 0x1C33: 84, + 0x1C36: 84, + 0x1C37: 84, + 0x1CD0: 84, + 0x1CD1: 84, + 0x1CD2: 84, + 0x1CD4: 84, + 0x1CD5: 84, + 0x1CD6: 84, + 0x1CD7: 84, + 0x1CD8: 84, + 0x1CD9: 84, + 0x1CDA: 84, + 0x1CDB: 84, + 0x1CDC: 84, + 0x1CDD: 84, + 0x1CDE: 84, + 0x1CDF: 84, + 0x1CE0: 84, + 0x1CE2: 84, + 0x1CE3: 84, + 0x1CE4: 84, + 0x1CE5: 84, + 0x1CE6: 84, + 0x1CE7: 84, + 0x1CE8: 84, + 0x1CED: 84, + 0x1CF4: 84, + 0x1CF8: 84, + 0x1CF9: 84, + 0x1DC0: 84, + 0x1DC1: 84, + 0x1DC2: 84, + 0x1DC3: 84, + 0x1DC4: 84, + 0x1DC5: 84, + 0x1DC6: 84, + 0x1DC7: 84, + 0x1DC8: 84, + 0x1DC9: 84, + 0x1DCA: 84, + 0x1DCB: 84, + 0x1DCC: 84, + 0x1DCD: 84, + 0x1DCE: 84, + 0x1DCF: 84, + 0x1DD0: 84, + 0x1DD1: 84, + 0x1DD2: 84, + 0x1DD3: 84, + 0x1DD4: 84, + 0x1DD5: 84, + 0x1DD6: 84, + 0x1DD7: 84, + 0x1DD8: 84, + 0x1DD9: 84, + 0x1DDA: 84, + 0x1DDB: 84, + 0x1DDC: 84, + 0x1DDD: 84, + 0x1DDE: 84, + 0x1DDF: 84, + 0x1DE0: 84, + 0x1DE1: 84, + 0x1DE2: 84, + 0x1DE3: 84, + 0x1DE4: 84, + 0x1DE5: 84, + 0x1DE6: 84, + 0x1DE7: 84, + 0x1DE8: 84, + 0x1DE9: 84, + 0x1DEA: 84, + 0x1DEB: 84, + 0x1DEC: 84, + 0x1DED: 84, + 0x1DEE: 84, + 0x1DEF: 84, + 0x1DF0: 84, + 0x1DF1: 84, + 0x1DF2: 84, + 0x1DF3: 84, + 0x1DF4: 84, + 0x1DF5: 84, + 0x1DF6: 84, + 0x1DF7: 84, + 0x1DF8: 84, + 0x1DF9: 84, + 0x1DFA: 84, + 0x1DFB: 84, + 0x1DFC: 84, + 0x1DFD: 84, + 0x1DFE: 84, + 0x1DFF: 84, + 0x200B: 84, + 0x200D: 67, + 0x200E: 84, + 0x200F: 84, + 0x202A: 84, + 0x202B: 84, + 0x202C: 84, + 0x202D: 84, + 0x202E: 84, + 0x2060: 84, + 0x2061: 84, + 0x2062: 84, + 0x2063: 84, + 0x2064: 84, + 0x206A: 84, + 0x206B: 84, + 0x206C: 84, + 0x206D: 84, + 0x206E: 84, + 0x206F: 84, + 0x20D0: 84, + 0x20D1: 84, + 0x20D2: 84, + 0x20D3: 84, + 0x20D4: 84, + 0x20D5: 84, + 0x20D6: 84, + 0x20D7: 84, + 0x20D8: 84, + 0x20D9: 84, + 0x20DA: 84, + 0x20DB: 84, + 0x20DC: 84, + 0x20DD: 84, + 0x20DE: 84, + 0x20DF: 84, + 0x20E0: 84, + 0x20E1: 84, + 0x20E2: 84, + 0x20E3: 84, + 0x20E4: 84, + 0x20E5: 84, + 0x20E6: 84, + 0x20E7: 84, + 0x20E8: 84, + 0x20E9: 84, + 0x20EA: 84, + 0x20EB: 84, + 0x20EC: 84, + 0x20ED: 84, + 0x20EE: 84, + 0x20EF: 84, + 0x20F0: 84, + 0x2CEF: 84, + 0x2CF0: 84, + 0x2CF1: 84, + 0x2D7F: 84, + 0x2DE0: 84, + 0x2DE1: 84, + 0x2DE2: 84, + 0x2DE3: 84, + 0x2DE4: 84, + 0x2DE5: 84, + 0x2DE6: 84, + 0x2DE7: 84, + 0x2DE8: 84, + 0x2DE9: 84, + 0x2DEA: 84, + 0x2DEB: 84, + 0x2DEC: 84, + 0x2DED: 84, + 0x2DEE: 84, + 0x2DEF: 84, + 0x2DF0: 84, + 0x2DF1: 84, + 0x2DF2: 84, + 0x2DF3: 84, + 0x2DF4: 84, + 0x2DF5: 84, + 0x2DF6: 84, + 0x2DF7: 84, + 0x2DF8: 84, + 0x2DF9: 84, + 0x2DFA: 84, + 0x2DFB: 84, + 0x2DFC: 84, + 0x2DFD: 84, + 0x2DFE: 84, + 0x2DFF: 84, + 0x302A: 84, + 0x302B: 84, + 0x302C: 84, + 0x302D: 84, + 0x3099: 84, + 0x309A: 84, + 0xA66F: 84, + 0xA670: 84, + 0xA671: 84, + 0xA672: 84, + 0xA674: 84, + 0xA675: 84, + 0xA676: 84, + 0xA677: 84, + 0xA678: 84, + 0xA679: 84, + 0xA67A: 84, + 0xA67B: 84, + 0xA67C: 84, + 0xA67D: 84, + 0xA69E: 84, + 0xA69F: 84, + 0xA6F0: 84, + 0xA6F1: 84, + 0xA802: 84, + 0xA806: 84, + 0xA80B: 84, + 0xA825: 84, + 0xA826: 84, + 0xA82C: 84, + 0xA840: 68, + 0xA841: 68, + 0xA842: 68, + 0xA843: 68, + 0xA844: 68, + 0xA845: 68, + 0xA846: 68, + 0xA847: 68, + 0xA848: 68, + 0xA849: 68, + 0xA84A: 68, + 0xA84B: 68, + 0xA84C: 68, + 0xA84D: 68, + 0xA84E: 68, + 0xA84F: 68, + 0xA850: 68, + 0xA851: 68, + 0xA852: 68, + 0xA853: 68, + 0xA854: 68, + 0xA855: 68, + 0xA856: 68, + 0xA857: 68, + 0xA858: 68, + 0xA859: 68, + 0xA85A: 68, + 0xA85B: 68, + 0xA85C: 68, + 0xA85D: 68, + 0xA85E: 68, + 0xA85F: 68, + 0xA860: 68, + 0xA861: 68, + 0xA862: 68, + 0xA863: 68, + 0xA864: 68, + 0xA865: 68, + 0xA866: 68, + 0xA867: 68, + 0xA868: 68, + 0xA869: 68, + 0xA86A: 68, + 0xA86B: 68, + 0xA86C: 68, + 0xA86D: 68, + 0xA86E: 68, + 0xA86F: 68, + 0xA870: 68, + 0xA871: 68, + 0xA872: 76, + 0xA8C4: 84, + 0xA8C5: 84, + 0xA8E0: 84, + 0xA8E1: 84, + 0xA8E2: 84, + 0xA8E3: 84, + 0xA8E4: 84, + 0xA8E5: 84, + 0xA8E6: 84, + 0xA8E7: 84, + 0xA8E8: 84, + 0xA8E9: 84, + 0xA8EA: 84, + 0xA8EB: 84, + 0xA8EC: 84, + 0xA8ED: 84, + 0xA8EE: 84, + 0xA8EF: 84, + 0xA8F0: 84, + 0xA8F1: 84, + 0xA8FF: 84, + 0xA926: 84, + 0xA927: 84, + 0xA928: 84, + 0xA929: 84, + 0xA92A: 84, + 0xA92B: 84, + 0xA92C: 84, + 0xA92D: 84, + 0xA947: 84, + 0xA948: 84, + 0xA949: 84, + 0xA94A: 84, + 0xA94B: 84, + 0xA94C: 84, + 0xA94D: 84, + 0xA94E: 84, + 0xA94F: 84, + 0xA950: 84, + 0xA951: 84, + 0xA980: 84, + 0xA981: 84, + 0xA982: 84, + 0xA9B3: 84, + 0xA9B6: 84, + 0xA9B7: 84, + 0xA9B8: 84, + 0xA9B9: 84, + 0xA9BC: 84, + 0xA9BD: 84, + 0xA9E5: 84, + 0xAA29: 84, + 0xAA2A: 84, + 0xAA2B: 84, + 0xAA2C: 84, + 0xAA2D: 84, + 0xAA2E: 84, + 0xAA31: 84, + 0xAA32: 84, + 0xAA35: 84, + 0xAA36: 84, + 0xAA43: 84, + 0xAA4C: 84, + 0xAA7C: 84, + 0xAAB0: 84, + 0xAAB2: 84, + 0xAAB3: 84, + 0xAAB4: 84, + 0xAAB7: 84, + 0xAAB8: 84, + 0xAABE: 84, + 0xAABF: 84, + 0xAAC1: 84, + 0xAAEC: 84, + 0xAAED: 84, + 0xAAF6: 84, + 0xABE5: 84, + 0xABE8: 84, + 0xABED: 84, + 0xFB1E: 84, + 0xFE00: 84, + 0xFE01: 84, + 0xFE02: 84, + 0xFE03: 84, + 0xFE04: 84, + 0xFE05: 84, + 0xFE06: 84, + 0xFE07: 84, + 0xFE08: 84, + 0xFE09: 84, + 0xFE0A: 84, + 0xFE0B: 84, + 0xFE0C: 84, + 0xFE0D: 84, + 0xFE0E: 84, + 0xFE0F: 84, + 0xFE20: 84, + 0xFE21: 84, + 0xFE22: 84, + 0xFE23: 84, + 0xFE24: 84, + 0xFE25: 84, + 0xFE26: 84, + 0xFE27: 84, + 0xFE28: 84, + 0xFE29: 84, + 0xFE2A: 84, + 0xFE2B: 84, + 0xFE2C: 84, + 0xFE2D: 84, + 0xFE2E: 84, + 0xFE2F: 84, + 0xFEFF: 84, + 0xFFF9: 84, + 0xFFFA: 84, + 0xFFFB: 84, + 0x101FD: 84, + 0x102E0: 84, + 0x10376: 84, + 0x10377: 84, + 0x10378: 84, + 0x10379: 84, + 0x1037A: 84, + 0x10A01: 84, + 0x10A02: 84, + 0x10A03: 84, + 0x10A05: 84, + 0x10A06: 84, + 0x10A0C: 84, + 0x10A0D: 84, + 0x10A0E: 84, + 0x10A0F: 84, + 0x10A38: 84, + 0x10A39: 84, + 0x10A3A: 84, + 0x10A3F: 84, + 0x10AC0: 68, + 0x10AC1: 68, + 0x10AC2: 68, + 0x10AC3: 68, + 0x10AC4: 68, + 0x10AC5: 82, + 0x10AC7: 82, + 0x10AC9: 82, + 0x10ACA: 82, + 0x10ACD: 76, + 0x10ACE: 82, + 0x10ACF: 82, + 0x10AD0: 82, + 0x10AD1: 82, + 0x10AD2: 82, + 0x10AD3: 68, + 0x10AD4: 68, + 0x10AD5: 68, + 0x10AD6: 68, + 0x10AD7: 76, + 0x10AD8: 68, + 0x10AD9: 68, + 0x10ADA: 68, + 0x10ADB: 68, + 0x10ADC: 68, + 0x10ADD: 82, + 0x10ADE: 68, + 0x10ADF: 68, + 0x10AE0: 68, + 0x10AE1: 82, + 0x10AE4: 82, + 0x10AE5: 84, + 0x10AE6: 84, + 0x10AEB: 68, + 0x10AEC: 68, + 0x10AED: 68, + 0x10AEE: 68, + 0x10AEF: 82, + 0x10B80: 68, + 0x10B81: 82, + 0x10B82: 68, + 0x10B83: 82, + 0x10B84: 82, + 0x10B85: 82, + 0x10B86: 68, + 0x10B87: 68, + 0x10B88: 68, + 0x10B89: 82, + 0x10B8A: 68, + 0x10B8B: 68, + 0x10B8C: 82, + 0x10B8D: 68, + 0x10B8E: 82, + 0x10B8F: 82, + 0x10B90: 68, + 0x10B91: 82, + 0x10BA9: 82, + 0x10BAA: 82, + 0x10BAB: 82, + 0x10BAC: 82, + 0x10BAD: 68, + 0x10BAE: 68, + 0x10D00: 76, + 0x10D01: 68, + 0x10D02: 68, + 0x10D03: 68, + 0x10D04: 68, + 0x10D05: 68, + 0x10D06: 68, + 0x10D07: 68, + 0x10D08: 68, + 0x10D09: 68, + 0x10D0A: 68, + 0x10D0B: 68, + 0x10D0C: 68, + 0x10D0D: 68, + 0x10D0E: 68, + 0x10D0F: 68, + 0x10D10: 68, + 0x10D11: 68, + 0x10D12: 68, + 0x10D13: 68, + 0x10D14: 68, + 0x10D15: 68, + 0x10D16: 68, + 0x10D17: 68, + 0x10D18: 68, + 0x10D19: 68, + 0x10D1A: 68, + 0x10D1B: 68, + 0x10D1C: 68, + 0x10D1D: 68, + 0x10D1E: 68, + 0x10D1F: 68, + 0x10D20: 68, + 0x10D21: 68, + 0x10D22: 82, + 0x10D23: 68, + 0x10D24: 84, + 0x10D25: 84, + 0x10D26: 84, + 0x10D27: 84, + 0x10EAB: 84, + 0x10EAC: 84, + 0x10EFD: 84, + 0x10EFE: 84, + 0x10EFF: 84, + 0x10F30: 68, + 0x10F31: 68, + 0x10F32: 68, + 0x10F33: 82, + 0x10F34: 68, + 0x10F35: 68, + 0x10F36: 68, + 0x10F37: 68, + 0x10F38: 68, + 0x10F39: 68, + 0x10F3A: 68, + 0x10F3B: 68, + 0x10F3C: 68, + 0x10F3D: 68, + 0x10F3E: 68, + 0x10F3F: 68, + 0x10F40: 68, + 0x10F41: 68, + 0x10F42: 68, + 0x10F43: 68, + 0x10F44: 68, + 0x10F46: 84, + 0x10F47: 84, + 0x10F48: 84, + 0x10F49: 84, + 0x10F4A: 84, + 0x10F4B: 84, + 0x10F4C: 84, + 0x10F4D: 84, + 0x10F4E: 84, + 0x10F4F: 84, + 0x10F50: 84, + 0x10F51: 68, + 0x10F52: 68, + 0x10F53: 68, + 0x10F54: 82, + 0x10F70: 68, + 0x10F71: 68, + 0x10F72: 68, + 0x10F73: 68, + 0x10F74: 82, + 0x10F75: 82, + 0x10F76: 68, + 0x10F77: 68, + 0x10F78: 68, + 0x10F79: 68, + 0x10F7A: 68, + 0x10F7B: 68, + 0x10F7C: 68, + 0x10F7D: 68, + 0x10F7E: 68, + 0x10F7F: 68, + 0x10F80: 68, + 0x10F81: 68, + 0x10F82: 84, + 0x10F83: 84, + 0x10F84: 84, + 0x10F85: 84, + 0x10FB0: 68, + 0x10FB2: 68, + 0x10FB3: 68, + 0x10FB4: 82, + 0x10FB5: 82, + 0x10FB6: 82, + 0x10FB8: 68, + 0x10FB9: 82, + 0x10FBA: 82, + 0x10FBB: 68, + 0x10FBC: 68, + 0x10FBD: 82, + 0x10FBE: 68, + 0x10FBF: 68, + 0x10FC1: 68, + 0x10FC2: 82, + 0x10FC3: 82, + 0x10FC4: 68, + 0x10FC9: 82, + 0x10FCA: 68, + 0x10FCB: 76, + 0x11001: 84, + 0x11038: 84, + 0x11039: 84, + 0x1103A: 84, + 0x1103B: 84, + 0x1103C: 84, + 0x1103D: 84, + 0x1103E: 84, + 0x1103F: 84, + 0x11040: 84, + 0x11041: 84, + 0x11042: 84, + 0x11043: 84, + 0x11044: 84, + 0x11045: 84, + 0x11046: 84, + 0x11070: 84, + 0x11073: 84, + 0x11074: 84, + 0x1107F: 84, + 0x11080: 84, + 0x11081: 84, + 0x110B3: 84, + 0x110B4: 84, + 0x110B5: 84, + 0x110B6: 84, + 0x110B9: 84, + 0x110BA: 84, + 0x110C2: 84, + 0x11100: 84, + 0x11101: 84, + 0x11102: 84, + 0x11127: 84, + 0x11128: 84, + 0x11129: 84, + 0x1112A: 84, + 0x1112B: 84, + 0x1112D: 84, + 0x1112E: 84, + 0x1112F: 84, + 0x11130: 84, + 0x11131: 84, + 0x11132: 84, + 0x11133: 84, + 0x11134: 84, + 0x11173: 84, + 0x11180: 84, + 0x11181: 84, + 0x111B6: 84, + 0x111B7: 84, + 0x111B8: 84, + 0x111B9: 84, + 0x111BA: 84, + 0x111BB: 84, + 0x111BC: 84, + 0x111BD: 84, + 0x111BE: 84, + 0x111C9: 84, + 0x111CA: 84, + 0x111CB: 84, + 0x111CC: 84, + 0x111CF: 84, + 0x1122F: 84, + 0x11230: 84, + 0x11231: 84, + 0x11234: 84, + 0x11236: 84, + 0x11237: 84, + 0x1123E: 84, + 0x11241: 84, + 0x112DF: 84, + 0x112E3: 84, + 0x112E4: 84, + 0x112E5: 84, + 0x112E6: 84, + 0x112E7: 84, + 0x112E8: 84, + 0x112E9: 84, + 0x112EA: 84, + 0x11300: 84, + 0x11301: 84, + 0x1133B: 84, + 0x1133C: 84, + 0x11340: 84, + 0x11366: 84, + 0x11367: 84, + 0x11368: 84, + 0x11369: 84, + 0x1136A: 84, + 0x1136B: 84, + 0x1136C: 84, + 0x11370: 84, + 0x11371: 84, + 0x11372: 84, + 0x11373: 84, + 0x11374: 84, + 0x11438: 84, + 0x11439: 84, + 0x1143A: 84, + 0x1143B: 84, + 0x1143C: 84, + 0x1143D: 84, + 0x1143E: 84, + 0x1143F: 84, + 0x11442: 84, + 0x11443: 84, + 0x11444: 84, + 0x11446: 84, + 0x1145E: 84, + 0x114B3: 84, + 0x114B4: 84, + 0x114B5: 84, + 0x114B6: 84, + 0x114B7: 84, + 0x114B8: 84, + 0x114BA: 84, + 0x114BF: 84, + 0x114C0: 84, + 0x114C2: 84, + 0x114C3: 84, + 0x115B2: 84, + 0x115B3: 84, + 0x115B4: 84, + 0x115B5: 84, + 0x115BC: 84, + 0x115BD: 84, + 0x115BF: 84, + 0x115C0: 84, + 0x115DC: 84, + 0x115DD: 84, + 0x11633: 84, + 0x11634: 84, + 0x11635: 84, + 0x11636: 84, + 0x11637: 84, + 0x11638: 84, + 0x11639: 84, + 0x1163A: 84, + 0x1163D: 84, + 0x1163F: 84, + 0x11640: 84, + 0x116AB: 84, + 0x116AD: 84, + 0x116B0: 84, + 0x116B1: 84, + 0x116B2: 84, + 0x116B3: 84, + 0x116B4: 84, + 0x116B5: 84, + 0x116B7: 84, + 0x1171D: 84, + 0x1171E: 84, + 0x1171F: 84, + 0x11722: 84, + 0x11723: 84, + 0x11724: 84, + 0x11725: 84, + 0x11727: 84, + 0x11728: 84, + 0x11729: 84, + 0x1172A: 84, + 0x1172B: 84, + 0x1182F: 84, + 0x11830: 84, + 0x11831: 84, + 0x11832: 84, + 0x11833: 84, + 0x11834: 84, + 0x11835: 84, + 0x11836: 84, + 0x11837: 84, + 0x11839: 84, + 0x1183A: 84, + 0x1193B: 84, + 0x1193C: 84, + 0x1193E: 84, + 0x11943: 84, + 0x119D4: 84, + 0x119D5: 84, + 0x119D6: 84, + 0x119D7: 84, + 0x119DA: 84, + 0x119DB: 84, + 0x119E0: 84, + 0x11A01: 84, + 0x11A02: 84, + 0x11A03: 84, + 0x11A04: 84, + 0x11A05: 84, + 0x11A06: 84, + 0x11A07: 84, + 0x11A08: 84, + 0x11A09: 84, + 0x11A0A: 84, + 0x11A33: 84, + 0x11A34: 84, + 0x11A35: 84, + 0x11A36: 84, + 0x11A37: 84, + 0x11A38: 84, + 0x11A3B: 84, + 0x11A3C: 84, + 0x11A3D: 84, + 0x11A3E: 84, + 0x11A47: 84, + 0x11A51: 84, + 0x11A52: 84, + 0x11A53: 84, + 0x11A54: 84, + 0x11A55: 84, + 0x11A56: 84, + 0x11A59: 84, + 0x11A5A: 84, + 0x11A5B: 84, + 0x11A8A: 84, + 0x11A8B: 84, + 0x11A8C: 84, + 0x11A8D: 84, + 0x11A8E: 84, + 0x11A8F: 84, + 0x11A90: 84, + 0x11A91: 84, + 0x11A92: 84, + 0x11A93: 84, + 0x11A94: 84, + 0x11A95: 84, + 0x11A96: 84, + 0x11A98: 84, + 0x11A99: 84, + 0x11C30: 84, + 0x11C31: 84, + 0x11C32: 84, + 0x11C33: 84, + 0x11C34: 84, + 0x11C35: 84, + 0x11C36: 84, + 0x11C38: 84, + 0x11C39: 84, + 0x11C3A: 84, + 0x11C3B: 84, + 0x11C3C: 84, + 0x11C3D: 84, + 0x11C3F: 84, + 0x11C92: 84, + 0x11C93: 84, + 0x11C94: 84, + 0x11C95: 84, + 0x11C96: 84, + 0x11C97: 84, + 0x11C98: 84, + 0x11C99: 84, + 0x11C9A: 84, + 0x11C9B: 84, + 0x11C9C: 84, + 0x11C9D: 84, + 0x11C9E: 84, + 0x11C9F: 84, + 0x11CA0: 84, + 0x11CA1: 84, + 0x11CA2: 84, + 0x11CA3: 84, + 0x11CA4: 84, + 0x11CA5: 84, + 0x11CA6: 84, + 0x11CA7: 84, + 0x11CAA: 84, + 0x11CAB: 84, + 0x11CAC: 84, + 0x11CAD: 84, + 0x11CAE: 84, + 0x11CAF: 84, + 0x11CB0: 84, + 0x11CB2: 84, + 0x11CB3: 84, + 0x11CB5: 84, + 0x11CB6: 84, + 0x11D31: 84, + 0x11D32: 84, + 0x11D33: 84, + 0x11D34: 84, + 0x11D35: 84, + 0x11D36: 84, + 0x11D3A: 84, + 0x11D3C: 84, + 0x11D3D: 84, + 0x11D3F: 84, + 0x11D40: 84, + 0x11D41: 84, + 0x11D42: 84, + 0x11D43: 84, + 0x11D44: 84, + 0x11D45: 84, + 0x11D47: 84, + 0x11D90: 84, + 0x11D91: 84, + 0x11D95: 84, + 0x11D97: 84, + 0x11EF3: 84, + 0x11EF4: 84, + 0x11F00: 84, + 0x11F01: 84, + 0x11F36: 84, + 0x11F37: 84, + 0x11F38: 84, + 0x11F39: 84, + 0x11F3A: 84, + 0x11F40: 84, + 0x11F42: 84, + 0x13430: 84, + 0x13431: 84, + 0x13432: 84, + 0x13433: 84, + 0x13434: 84, + 0x13435: 84, + 0x13436: 84, + 0x13437: 84, + 0x13438: 84, + 0x13439: 84, + 0x1343A: 84, + 0x1343B: 84, + 0x1343C: 84, + 0x1343D: 84, + 0x1343E: 84, + 0x1343F: 84, + 0x13440: 84, + 0x13447: 84, + 0x13448: 84, + 0x13449: 84, + 0x1344A: 84, + 0x1344B: 84, + 0x1344C: 84, + 0x1344D: 84, + 0x1344E: 84, + 0x1344F: 84, + 0x13450: 84, + 0x13451: 84, + 0x13452: 84, + 0x13453: 84, + 0x13454: 84, + 0x13455: 84, + 0x16AF0: 84, + 0x16AF1: 84, + 0x16AF2: 84, + 0x16AF3: 84, + 0x16AF4: 84, + 0x16B30: 84, + 0x16B31: 84, + 0x16B32: 84, + 0x16B33: 84, + 0x16B34: 84, + 0x16B35: 84, + 0x16B36: 84, + 0x16F4F: 84, + 0x16F8F: 84, + 0x16F90: 84, + 0x16F91: 84, + 0x16F92: 84, + 0x16FE4: 84, + 0x1BC9D: 84, + 0x1BC9E: 84, + 0x1BCA0: 84, + 0x1BCA1: 84, + 0x1BCA2: 84, + 0x1BCA3: 84, + 0x1CF00: 84, + 0x1CF01: 84, + 0x1CF02: 84, + 0x1CF03: 84, + 0x1CF04: 84, + 0x1CF05: 84, + 0x1CF06: 84, + 0x1CF07: 84, + 0x1CF08: 84, + 0x1CF09: 84, + 0x1CF0A: 84, + 0x1CF0B: 84, + 0x1CF0C: 84, + 0x1CF0D: 84, + 0x1CF0E: 84, + 0x1CF0F: 84, + 0x1CF10: 84, + 0x1CF11: 84, + 0x1CF12: 84, + 0x1CF13: 84, + 0x1CF14: 84, + 0x1CF15: 84, + 0x1CF16: 84, + 0x1CF17: 84, + 0x1CF18: 84, + 0x1CF19: 84, + 0x1CF1A: 84, + 0x1CF1B: 84, + 0x1CF1C: 84, + 0x1CF1D: 84, + 0x1CF1E: 84, + 0x1CF1F: 84, + 0x1CF20: 84, + 0x1CF21: 84, + 0x1CF22: 84, + 0x1CF23: 84, + 0x1CF24: 84, + 0x1CF25: 84, + 0x1CF26: 84, + 0x1CF27: 84, + 0x1CF28: 84, + 0x1CF29: 84, + 0x1CF2A: 84, + 0x1CF2B: 84, + 0x1CF2C: 84, + 0x1CF2D: 84, + 0x1CF30: 84, + 0x1CF31: 84, + 0x1CF32: 84, + 0x1CF33: 84, + 0x1CF34: 84, + 0x1CF35: 84, + 0x1CF36: 84, + 0x1CF37: 84, + 0x1CF38: 84, + 0x1CF39: 84, + 0x1CF3A: 84, + 0x1CF3B: 84, + 0x1CF3C: 84, + 0x1CF3D: 84, + 0x1CF3E: 84, + 0x1CF3F: 84, + 0x1CF40: 84, + 0x1CF41: 84, + 0x1CF42: 84, + 0x1CF43: 84, + 0x1CF44: 84, + 0x1CF45: 84, + 0x1CF46: 84, + 0x1D167: 84, + 0x1D168: 84, + 0x1D169: 84, + 0x1D173: 84, + 0x1D174: 84, + 0x1D175: 84, + 0x1D176: 84, + 0x1D177: 84, + 0x1D178: 84, + 0x1D179: 84, + 0x1D17A: 84, + 0x1D17B: 84, + 0x1D17C: 84, + 0x1D17D: 84, + 0x1D17E: 84, + 0x1D17F: 84, + 0x1D180: 84, + 0x1D181: 84, + 0x1D182: 84, + 0x1D185: 84, + 0x1D186: 84, + 0x1D187: 84, + 0x1D188: 84, + 0x1D189: 84, + 0x1D18A: 84, + 0x1D18B: 84, + 0x1D1AA: 84, + 0x1D1AB: 84, + 0x1D1AC: 84, + 0x1D1AD: 84, + 0x1D242: 84, + 0x1D243: 84, + 0x1D244: 84, + 0x1DA00: 84, + 0x1DA01: 84, + 0x1DA02: 84, + 0x1DA03: 84, + 0x1DA04: 84, + 0x1DA05: 84, + 0x1DA06: 84, + 0x1DA07: 84, + 0x1DA08: 84, + 0x1DA09: 84, + 0x1DA0A: 84, + 0x1DA0B: 84, + 0x1DA0C: 84, + 0x1DA0D: 84, + 0x1DA0E: 84, + 0x1DA0F: 84, + 0x1DA10: 84, + 0x1DA11: 84, + 0x1DA12: 84, + 0x1DA13: 84, + 0x1DA14: 84, + 0x1DA15: 84, + 0x1DA16: 84, + 0x1DA17: 84, + 0x1DA18: 84, + 0x1DA19: 84, + 0x1DA1A: 84, + 0x1DA1B: 84, + 0x1DA1C: 84, + 0x1DA1D: 84, + 0x1DA1E: 84, + 0x1DA1F: 84, + 0x1DA20: 84, + 0x1DA21: 84, + 0x1DA22: 84, + 0x1DA23: 84, + 0x1DA24: 84, + 0x1DA25: 84, + 0x1DA26: 84, + 0x1DA27: 84, + 0x1DA28: 84, + 0x1DA29: 84, + 0x1DA2A: 84, + 0x1DA2B: 84, + 0x1DA2C: 84, + 0x1DA2D: 84, + 0x1DA2E: 84, + 0x1DA2F: 84, + 0x1DA30: 84, + 0x1DA31: 84, + 0x1DA32: 84, + 0x1DA33: 84, + 0x1DA34: 84, + 0x1DA35: 84, + 0x1DA36: 84, + 0x1DA3B: 84, + 0x1DA3C: 84, + 0x1DA3D: 84, + 0x1DA3E: 84, + 0x1DA3F: 84, + 0x1DA40: 84, + 0x1DA41: 84, + 0x1DA42: 84, + 0x1DA43: 84, + 0x1DA44: 84, + 0x1DA45: 84, + 0x1DA46: 84, + 0x1DA47: 84, + 0x1DA48: 84, + 0x1DA49: 84, + 0x1DA4A: 84, + 0x1DA4B: 84, + 0x1DA4C: 84, + 0x1DA4D: 84, + 0x1DA4E: 84, + 0x1DA4F: 84, + 0x1DA50: 84, + 0x1DA51: 84, + 0x1DA52: 84, + 0x1DA53: 84, + 0x1DA54: 84, + 0x1DA55: 84, + 0x1DA56: 84, + 0x1DA57: 84, + 0x1DA58: 84, + 0x1DA59: 84, + 0x1DA5A: 84, + 0x1DA5B: 84, + 0x1DA5C: 84, + 0x1DA5D: 84, + 0x1DA5E: 84, + 0x1DA5F: 84, + 0x1DA60: 84, + 0x1DA61: 84, + 0x1DA62: 84, + 0x1DA63: 84, + 0x1DA64: 84, + 0x1DA65: 84, + 0x1DA66: 84, + 0x1DA67: 84, + 0x1DA68: 84, + 0x1DA69: 84, + 0x1DA6A: 84, + 0x1DA6B: 84, + 0x1DA6C: 84, + 0x1DA75: 84, + 0x1DA84: 84, + 0x1DA9B: 84, + 0x1DA9C: 84, + 0x1DA9D: 84, + 0x1DA9E: 84, + 0x1DA9F: 84, + 0x1DAA1: 84, + 0x1DAA2: 84, + 0x1DAA3: 84, + 0x1DAA4: 84, + 0x1DAA5: 84, + 0x1DAA6: 84, + 0x1DAA7: 84, + 0x1DAA8: 84, + 0x1DAA9: 84, + 0x1DAAA: 84, + 0x1DAAB: 84, + 0x1DAAC: 84, + 0x1DAAD: 84, + 0x1DAAE: 84, + 0x1DAAF: 84, + 0x1E000: 84, + 0x1E001: 84, + 0x1E002: 84, + 0x1E003: 84, + 0x1E004: 84, + 0x1E005: 84, + 0x1E006: 84, + 0x1E008: 84, + 0x1E009: 84, + 0x1E00A: 84, + 0x1E00B: 84, + 0x1E00C: 84, + 0x1E00D: 84, + 0x1E00E: 84, + 0x1E00F: 84, + 0x1E010: 84, + 0x1E011: 84, + 0x1E012: 84, + 0x1E013: 84, + 0x1E014: 84, + 0x1E015: 84, + 0x1E016: 84, + 0x1E017: 84, + 0x1E018: 84, + 0x1E01B: 84, + 0x1E01C: 84, + 0x1E01D: 84, + 0x1E01E: 84, + 0x1E01F: 84, + 0x1E020: 84, + 0x1E021: 84, + 0x1E023: 84, + 0x1E024: 84, + 0x1E026: 84, + 0x1E027: 84, + 0x1E028: 84, + 0x1E029: 84, + 0x1E02A: 84, + 0x1E08F: 84, + 0x1E130: 84, + 0x1E131: 84, + 0x1E132: 84, + 0x1E133: 84, + 0x1E134: 84, + 0x1E135: 84, + 0x1E136: 84, + 0x1E2AE: 84, + 0x1E2EC: 84, + 0x1E2ED: 84, + 0x1E2EE: 84, + 0x1E2EF: 84, + 0x1E4EC: 84, + 0x1E4ED: 84, + 0x1E4EE: 84, + 0x1E4EF: 84, + 0x1E8D0: 84, + 0x1E8D1: 84, + 0x1E8D2: 84, + 0x1E8D3: 84, + 0x1E8D4: 84, + 0x1E8D5: 84, + 0x1E8D6: 84, + 0x1E900: 68, + 0x1E901: 68, + 0x1E902: 68, + 0x1E903: 68, + 0x1E904: 68, + 0x1E905: 68, + 0x1E906: 68, + 0x1E907: 68, + 0x1E908: 68, + 0x1E909: 68, + 0x1E90A: 68, + 0x1E90B: 68, + 0x1E90C: 68, + 0x1E90D: 68, + 0x1E90E: 68, + 0x1E90F: 68, + 0x1E910: 68, + 0x1E911: 68, + 0x1E912: 68, + 0x1E913: 68, + 0x1E914: 68, + 0x1E915: 68, + 0x1E916: 68, + 0x1E917: 68, + 0x1E918: 68, + 0x1E919: 68, + 0x1E91A: 68, + 0x1E91B: 68, + 0x1E91C: 68, + 0x1E91D: 68, + 0x1E91E: 68, + 0x1E91F: 68, + 0x1E920: 68, + 0x1E921: 68, + 0x1E922: 68, + 0x1E923: 68, + 0x1E924: 68, + 0x1E925: 68, + 0x1E926: 68, + 0x1E927: 68, + 0x1E928: 68, + 0x1E929: 68, + 0x1E92A: 68, + 0x1E92B: 68, + 0x1E92C: 68, + 0x1E92D: 68, + 0x1E92E: 68, + 0x1E92F: 68, + 0x1E930: 68, + 0x1E931: 68, + 0x1E932: 68, + 0x1E933: 68, + 0x1E934: 68, + 0x1E935: 68, + 0x1E936: 68, + 0x1E937: 68, + 0x1E938: 68, + 0x1E939: 68, + 0x1E93A: 68, + 0x1E93B: 68, + 0x1E93C: 68, + 0x1E93D: 68, + 0x1E93E: 68, + 0x1E93F: 68, + 0x1E940: 68, + 0x1E941: 68, + 0x1E942: 68, + 0x1E943: 68, + 0x1E944: 84, + 0x1E945: 84, + 0x1E946: 84, + 0x1E947: 84, + 0x1E948: 84, + 0x1E949: 84, + 0x1E94A: 84, + 0x1E94B: 84, + 0xE0001: 84, + 0xE0020: 84, + 0xE0021: 84, + 0xE0022: 84, + 0xE0023: 84, + 0xE0024: 84, + 0xE0025: 84, + 0xE0026: 84, + 0xE0027: 84, + 0xE0028: 84, + 0xE0029: 84, + 0xE002A: 84, + 0xE002B: 84, + 0xE002C: 84, + 0xE002D: 84, + 0xE002E: 84, + 0xE002F: 84, + 0xE0030: 84, + 0xE0031: 84, + 0xE0032: 84, + 0xE0033: 84, + 0xE0034: 84, + 0xE0035: 84, + 0xE0036: 84, + 0xE0037: 84, + 0xE0038: 84, + 0xE0039: 84, + 0xE003A: 84, + 0xE003B: 84, + 0xE003C: 84, + 0xE003D: 84, + 0xE003E: 84, + 0xE003F: 84, + 0xE0040: 84, + 0xE0041: 84, + 0xE0042: 84, + 0xE0043: 84, + 0xE0044: 84, + 0xE0045: 84, + 0xE0046: 84, + 0xE0047: 84, + 0xE0048: 84, + 0xE0049: 84, + 0xE004A: 84, + 0xE004B: 84, + 0xE004C: 84, + 0xE004D: 84, + 0xE004E: 84, + 0xE004F: 84, + 0xE0050: 84, + 0xE0051: 84, + 0xE0052: 84, + 0xE0053: 84, + 0xE0054: 84, + 0xE0055: 84, + 0xE0056: 84, + 0xE0057: 84, + 0xE0058: 84, + 0xE0059: 84, + 0xE005A: 84, + 0xE005B: 84, + 0xE005C: 84, + 0xE005D: 84, + 0xE005E: 84, + 0xE005F: 84, + 0xE0060: 84, + 0xE0061: 84, + 0xE0062: 84, + 0xE0063: 84, + 0xE0064: 84, + 0xE0065: 84, + 0xE0066: 84, + 0xE0067: 84, + 0xE0068: 84, + 0xE0069: 84, + 0xE006A: 84, + 0xE006B: 84, + 0xE006C: 84, + 0xE006D: 84, + 0xE006E: 84, + 0xE006F: 84, + 0xE0070: 84, + 0xE0071: 84, + 0xE0072: 84, + 0xE0073: 84, + 0xE0074: 84, + 0xE0075: 84, + 0xE0076: 84, + 0xE0077: 84, + 0xE0078: 84, + 0xE0079: 84, + 0xE007A: 84, + 0xE007B: 84, + 0xE007C: 84, + 0xE007D: 84, + 0xE007E: 84, + 0xE007F: 84, + 0xE0100: 84, + 0xE0101: 84, + 0xE0102: 84, + 0xE0103: 84, + 0xE0104: 84, + 0xE0105: 84, + 0xE0106: 84, + 0xE0107: 84, + 0xE0108: 84, + 0xE0109: 84, + 0xE010A: 84, + 0xE010B: 84, + 0xE010C: 84, + 0xE010D: 84, + 0xE010E: 84, + 0xE010F: 84, + 0xE0110: 84, + 0xE0111: 84, + 0xE0112: 84, + 0xE0113: 84, + 0xE0114: 84, + 0xE0115: 84, + 0xE0116: 84, + 0xE0117: 84, + 0xE0118: 84, + 0xE0119: 84, + 0xE011A: 84, + 0xE011B: 84, + 0xE011C: 84, + 0xE011D: 84, + 0xE011E: 84, + 0xE011F: 84, + 0xE0120: 84, + 0xE0121: 84, + 0xE0122: 84, + 0xE0123: 84, + 0xE0124: 84, + 0xE0125: 84, + 0xE0126: 84, + 0xE0127: 84, + 0xE0128: 84, + 0xE0129: 84, + 0xE012A: 84, + 0xE012B: 84, + 0xE012C: 84, + 0xE012D: 84, + 0xE012E: 84, + 0xE012F: 84, + 0xE0130: 84, + 0xE0131: 84, + 0xE0132: 84, + 0xE0133: 84, + 0xE0134: 84, + 0xE0135: 84, + 0xE0136: 84, + 0xE0137: 84, + 0xE0138: 84, + 0xE0139: 84, + 0xE013A: 84, + 0xE013B: 84, + 0xE013C: 84, + 0xE013D: 84, + 0xE013E: 84, + 0xE013F: 84, + 0xE0140: 84, + 0xE0141: 84, + 0xE0142: 84, + 0xE0143: 84, + 0xE0144: 84, + 0xE0145: 84, + 0xE0146: 84, + 0xE0147: 84, + 0xE0148: 84, + 0xE0149: 84, + 0xE014A: 84, + 0xE014B: 84, + 0xE014C: 84, + 0xE014D: 84, + 0xE014E: 84, + 0xE014F: 84, + 0xE0150: 84, + 0xE0151: 84, + 0xE0152: 84, + 0xE0153: 84, + 0xE0154: 84, + 0xE0155: 84, + 0xE0156: 84, + 0xE0157: 84, + 0xE0158: 84, + 0xE0159: 84, + 0xE015A: 84, + 0xE015B: 84, + 0xE015C: 84, + 0xE015D: 84, + 0xE015E: 84, + 0xE015F: 84, + 0xE0160: 84, + 0xE0161: 84, + 0xE0162: 84, + 0xE0163: 84, + 0xE0164: 84, + 0xE0165: 84, + 0xE0166: 84, + 0xE0167: 84, + 0xE0168: 84, + 0xE0169: 84, + 0xE016A: 84, + 0xE016B: 84, + 0xE016C: 84, + 0xE016D: 84, + 0xE016E: 84, + 0xE016F: 84, + 0xE0170: 84, + 0xE0171: 84, + 0xE0172: 84, + 0xE0173: 84, + 0xE0174: 84, + 0xE0175: 84, + 0xE0176: 84, + 0xE0177: 84, + 0xE0178: 84, + 0xE0179: 84, + 0xE017A: 84, + 0xE017B: 84, + 0xE017C: 84, + 0xE017D: 84, + 0xE017E: 84, + 0xE017F: 84, + 0xE0180: 84, + 0xE0181: 84, + 0xE0182: 84, + 0xE0183: 84, + 0xE0184: 84, + 0xE0185: 84, + 0xE0186: 84, + 0xE0187: 84, + 0xE0188: 84, + 0xE0189: 84, + 0xE018A: 84, + 0xE018B: 84, + 0xE018C: 84, + 0xE018D: 84, + 0xE018E: 84, + 0xE018F: 84, + 0xE0190: 84, + 0xE0191: 84, + 0xE0192: 84, + 0xE0193: 84, + 0xE0194: 84, + 0xE0195: 84, + 0xE0196: 84, + 0xE0197: 84, + 0xE0198: 84, + 0xE0199: 84, + 0xE019A: 84, + 0xE019B: 84, + 0xE019C: 84, + 0xE019D: 84, + 0xE019E: 84, + 0xE019F: 84, + 0xE01A0: 84, + 0xE01A1: 84, + 0xE01A2: 84, + 0xE01A3: 84, + 0xE01A4: 84, + 0xE01A5: 84, + 0xE01A6: 84, + 0xE01A7: 84, + 0xE01A8: 84, + 0xE01A9: 84, + 0xE01AA: 84, + 0xE01AB: 84, + 0xE01AC: 84, + 0xE01AD: 84, + 0xE01AE: 84, + 0xE01AF: 84, + 0xE01B0: 84, + 0xE01B1: 84, + 0xE01B2: 84, + 0xE01B3: 84, + 0xE01B4: 84, + 0xE01B5: 84, + 0xE01B6: 84, + 0xE01B7: 84, + 0xE01B8: 84, + 0xE01B9: 84, + 0xE01BA: 84, + 0xE01BB: 84, + 0xE01BC: 84, + 0xE01BD: 84, + 0xE01BE: 84, + 0xE01BF: 84, + 0xE01C0: 84, + 0xE01C1: 84, + 0xE01C2: 84, + 0xE01C3: 84, + 0xE01C4: 84, + 0xE01C5: 84, + 0xE01C6: 84, + 0xE01C7: 84, + 0xE01C8: 84, + 0xE01C9: 84, + 0xE01CA: 84, + 0xE01CB: 84, + 0xE01CC: 84, + 0xE01CD: 84, + 0xE01CE: 84, + 0xE01CF: 84, + 0xE01D0: 84, + 0xE01D1: 84, + 0xE01D2: 84, + 0xE01D3: 84, + 0xE01D4: 84, + 0xE01D5: 84, + 0xE01D6: 84, + 0xE01D7: 84, + 0xE01D8: 84, + 0xE01D9: 84, + 0xE01DA: 84, + 0xE01DB: 84, + 0xE01DC: 84, + 0xE01DD: 84, + 0xE01DE: 84, + 0xE01DF: 84, + 0xE01E0: 84, + 0xE01E1: 84, + 0xE01E2: 84, + 0xE01E3: 84, + 0xE01E4: 84, + 0xE01E5: 84, + 0xE01E6: 84, + 0xE01E7: 84, + 0xE01E8: 84, + 0xE01E9: 84, + 0xE01EA: 84, + 0xE01EB: 84, + 0xE01EC: 84, + 0xE01ED: 84, + 0xE01EE: 84, + 0xE01EF: 84, +} +codepoint_classes = { + "PVALID": ( + 0x2D0000002E, + 0x300000003A, + 0x610000007B, + 0xDF000000F7, + 0xF800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010A, + 0x10B0000010C, + 0x10D0000010E, + 0x10F00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011A, + 0x11B0000011C, + 0x11D0000011E, + 0x11F00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012A, + 0x12B0000012C, + 0x12D0000012E, + 0x12F00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13A0000013B, + 0x13C0000013D, + 0x13E0000013F, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14B0000014C, + 0x14D0000014E, + 0x14F00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015A, + 0x15B0000015C, + 0x15D0000015E, + 0x15F00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016A, + 0x16B0000016C, + 0x16D0000016E, + 0x16F00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17A0000017B, + 0x17C0000017D, + 0x17E0000017F, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18C0000018E, + 0x19200000193, + 0x19500000196, + 0x1990000019C, + 0x19E0000019F, + 0x1A1000001A2, + 0x1A3000001A4, + 0x1A5000001A6, + 0x1A8000001A9, + 0x1AA000001AC, + 0x1AD000001AE, + 0x1B0000001B1, + 0x1B4000001B5, + 0x1B6000001B7, + 0x1B9000001BC, + 0x1BD000001C4, + 0x1CE000001CF, + 0x1D0000001D1, + 0x1D2000001D3, + 0x1D4000001D5, + 0x1D6000001D7, + 0x1D8000001D9, + 0x1DA000001DB, + 0x1DC000001DE, + 0x1DF000001E0, + 0x1E1000001E2, + 0x1E3000001E4, + 0x1E5000001E6, + 0x1E7000001E8, + 0x1E9000001EA, + 0x1EB000001EC, + 0x1ED000001EE, + 0x1EF000001F1, + 0x1F5000001F6, + 0x1F9000001FA, + 0x1FB000001FC, + 0x1FD000001FE, + 0x1FF00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020A, + 0x20B0000020C, + 0x20D0000020E, + 0x20F00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021A, + 0x21B0000021C, + 0x21D0000021E, + 0x21F00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022A, + 0x22B0000022C, + 0x22D0000022E, + 0x22F00000230, + 0x23100000232, + 0x2330000023A, + 0x23C0000023D, + 0x23F00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024A, + 0x24B0000024C, + 0x24D0000024E, + 0x24F000002B0, + 0x2B9000002C2, + 0x2C6000002D2, + 0x2EC000002ED, + 0x2EE000002EF, + 0x30000000340, + 0x34200000343, + 0x3460000034F, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37B0000037E, + 0x39000000391, + 0x3AC000003CF, + 0x3D7000003D8, + 0x3D9000003DA, + 0x3DB000003DC, + 0x3DD000003DE, + 0x3DF000003E0, + 0x3E1000003E2, + 0x3E3000003E4, + 0x3E5000003E6, + 0x3E7000003E8, + 0x3E9000003EA, + 0x3EB000003EC, + 0x3ED000003EE, + 0x3EF000003F0, + 0x3F3000003F4, + 0x3F8000003F9, + 0x3FB000003FD, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046A, + 0x46B0000046C, + 0x46D0000046E, + 0x46F00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047A, + 0x47B0000047C, + 0x47D0000047E, + 0x47F00000480, + 0x48100000482, + 0x48300000488, + 0x48B0000048C, + 0x48D0000048E, + 0x48F00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049A, + 0x49B0000049C, + 0x49D0000049E, + 0x49F000004A0, + 0x4A1000004A2, + 0x4A3000004A4, + 0x4A5000004A6, + 0x4A7000004A8, + 0x4A9000004AA, + 0x4AB000004AC, + 0x4AD000004AE, + 0x4AF000004B0, + 0x4B1000004B2, + 0x4B3000004B4, + 0x4B5000004B6, + 0x4B7000004B8, + 0x4B9000004BA, + 0x4BB000004BC, + 0x4BD000004BE, + 0x4BF000004C0, + 0x4C2000004C3, + 0x4C4000004C5, + 0x4C6000004C7, + 0x4C8000004C9, + 0x4CA000004CB, + 0x4CC000004CD, + 0x4CE000004D0, + 0x4D1000004D2, + 0x4D3000004D4, + 0x4D5000004D6, + 0x4D7000004D8, + 0x4D9000004DA, + 0x4DB000004DC, + 0x4DD000004DE, + 0x4DF000004E0, + 0x4E1000004E2, + 0x4E3000004E4, + 0x4E5000004E6, + 0x4E7000004E8, + 0x4E9000004EA, + 0x4EB000004EC, + 0x4ED000004EE, + 0x4EF000004F0, + 0x4F1000004F2, + 0x4F3000004F4, + 0x4F5000004F6, + 0x4F7000004F8, + 0x4F9000004FA, + 0x4FB000004FC, + 0x4FD000004FE, + 0x4FF00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050A, + 0x50B0000050C, + 0x50D0000050E, + 0x50F00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051A, + 0x51B0000051C, + 0x51D0000051E, + 0x51F00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052A, + 0x52B0000052C, + 0x52D0000052E, + 0x52F00000530, + 0x5590000055A, + 0x56000000587, + 0x58800000589, + 0x591000005BE, + 0x5BF000005C0, + 0x5C1000005C3, + 0x5C4000005C6, + 0x5C7000005C8, + 0x5D0000005EB, + 0x5EF000005F3, + 0x6100000061B, + 0x62000000640, + 0x64100000660, + 0x66E00000675, + 0x679000006D4, + 0x6D5000006DD, + 0x6DF000006E9, + 0x6EA000006F0, + 0x6FA00000700, + 0x7100000074B, + 0x74D000007B2, + 0x7C0000007F6, + 0x7FD000007FE, + 0x8000000082E, + 0x8400000085C, + 0x8600000086B, + 0x87000000888, + 0x8890000088F, + 0x898000008E2, + 0x8E300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098D, + 0x98F00000991, + 0x993000009A9, + 0x9AA000009B1, + 0x9B2000009B3, + 0x9B6000009BA, + 0x9BC000009C5, + 0x9C7000009C9, + 0x9CB000009CF, + 0x9D7000009D8, + 0x9E0000009E4, + 0x9E6000009F2, + 0x9FC000009FD, + 0x9FE000009FF, + 0xA0100000A04, + 0xA0500000A0B, + 0xA0F00000A11, + 0xA1300000A29, + 0xA2A00000A31, + 0xA3200000A33, + 0xA3500000A36, + 0xA3800000A3A, + 0xA3C00000A3D, + 0xA3E00000A43, + 0xA4700000A49, + 0xA4B00000A4E, + 0xA5100000A52, + 0xA5C00000A5D, + 0xA6600000A76, + 0xA8100000A84, + 0xA8500000A8E, + 0xA8F00000A92, + 0xA9300000AA9, + 0xAAA00000AB1, + 0xAB200000AB4, + 0xAB500000ABA, + 0xABC00000AC6, + 0xAC700000ACA, + 0xACB00000ACE, + 0xAD000000AD1, + 0xAE000000AE4, + 0xAE600000AF0, + 0xAF900000B00, + 0xB0100000B04, + 0xB0500000B0D, + 0xB0F00000B11, + 0xB1300000B29, + 0xB2A00000B31, + 0xB3200000B34, + 0xB3500000B3A, + 0xB3C00000B45, + 0xB4700000B49, + 0xB4B00000B4E, + 0xB5500000B58, + 0xB5F00000B64, + 0xB6600000B70, + 0xB7100000B72, + 0xB8200000B84, + 0xB8500000B8B, + 0xB8E00000B91, + 0xB9200000B96, + 0xB9900000B9B, + 0xB9C00000B9D, + 0xB9E00000BA0, + 0xBA300000BA5, + 0xBA800000BAB, + 0xBAE00000BBA, + 0xBBE00000BC3, + 0xBC600000BC9, + 0xBCA00000BCE, + 0xBD000000BD1, + 0xBD700000BD8, + 0xBE600000BF0, + 0xC0000000C0D, + 0xC0E00000C11, + 0xC1200000C29, + 0xC2A00000C3A, + 0xC3C00000C45, + 0xC4600000C49, + 0xC4A00000C4E, + 0xC5500000C57, + 0xC5800000C5B, + 0xC5D00000C5E, + 0xC6000000C64, + 0xC6600000C70, + 0xC8000000C84, + 0xC8500000C8D, + 0xC8E00000C91, + 0xC9200000CA9, + 0xCAA00000CB4, + 0xCB500000CBA, + 0xCBC00000CC5, + 0xCC600000CC9, + 0xCCA00000CCE, + 0xCD500000CD7, + 0xCDD00000CDF, + 0xCE000000CE4, + 0xCE600000CF0, + 0xCF100000CF4, + 0xD0000000D0D, + 0xD0E00000D11, + 0xD1200000D45, + 0xD4600000D49, + 0xD4A00000D4F, + 0xD5400000D58, + 0xD5F00000D64, + 0xD6600000D70, + 0xD7A00000D80, + 0xD8100000D84, + 0xD8500000D97, + 0xD9A00000DB2, + 0xDB300000DBC, + 0xDBD00000DBE, + 0xDC000000DC7, + 0xDCA00000DCB, + 0xDCF00000DD5, + 0xDD600000DD7, + 0xDD800000DE0, + 0xDE600000DF0, + 0xDF200000DF4, + 0xE0100000E33, + 0xE3400000E3B, + 0xE4000000E4F, + 0xE5000000E5A, + 0xE8100000E83, + 0xE8400000E85, + 0xE8600000E8B, + 0xE8C00000EA4, + 0xEA500000EA6, + 0xEA700000EB3, + 0xEB400000EBE, + 0xEC000000EC5, + 0xEC600000EC7, + 0xEC800000ECF, + 0xED000000EDA, + 0xEDE00000EE0, + 0xF0000000F01, + 0xF0B00000F0C, + 0xF1800000F1A, + 0xF2000000F2A, + 0xF3500000F36, + 0xF3700000F38, + 0xF3900000F3A, + 0xF3E00000F43, + 0xF4400000F48, + 0xF4900000F4D, + 0xF4E00000F52, + 0xF5300000F57, + 0xF5800000F5C, + 0xF5D00000F69, + 0xF6A00000F6D, + 0xF7100000F73, + 0xF7400000F75, + 0xF7A00000F81, + 0xF8200000F85, + 0xF8600000F93, + 0xF9400000F98, + 0xF9900000F9D, + 0xF9E00000FA2, + 0xFA300000FA7, + 0xFA800000FAC, + 0xFAD00000FB9, + 0xFBA00000FBD, + 0xFC600000FC7, + 0x10000000104A, + 0x10500000109E, + 0x10D0000010FB, + 0x10FD00001100, + 0x120000001249, + 0x124A0000124E, + 0x125000001257, + 0x125800001259, + 0x125A0000125E, + 0x126000001289, + 0x128A0000128E, + 0x1290000012B1, + 0x12B2000012B6, + 0x12B8000012BF, + 0x12C0000012C1, + 0x12C2000012C6, + 0x12C8000012D7, + 0x12D800001311, + 0x131200001316, + 0x13180000135B, + 0x135D00001360, + 0x138000001390, + 0x13A0000013F6, + 0x14010000166D, + 0x166F00001680, + 0x16810000169B, + 0x16A0000016EB, + 0x16F1000016F9, + 0x170000001716, + 0x171F00001735, + 0x174000001754, + 0x17600000176D, + 0x176E00001771, + 0x177200001774, + 0x1780000017B4, + 0x17B6000017D4, + 0x17D7000017D8, + 0x17DC000017DE, + 0x17E0000017EA, + 0x18100000181A, + 0x182000001879, + 0x1880000018AB, + 0x18B0000018F6, + 0x19000000191F, + 0x19200000192C, + 0x19300000193C, + 0x19460000196E, + 0x197000001975, + 0x1980000019AC, + 0x19B0000019CA, + 0x19D0000019DA, + 0x1A0000001A1C, + 0x1A2000001A5F, + 0x1A6000001A7D, + 0x1A7F00001A8A, + 0x1A9000001A9A, + 0x1AA700001AA8, + 0x1AB000001ABE, + 0x1ABF00001ACF, + 0x1B0000001B4D, + 0x1B5000001B5A, + 0x1B6B00001B74, + 0x1B8000001BF4, + 0x1C0000001C38, + 0x1C4000001C4A, + 0x1C4D00001C7E, + 0x1CD000001CD3, + 0x1CD400001CFB, + 0x1D0000001D2C, + 0x1D2F00001D30, + 0x1D3B00001D3C, + 0x1D4E00001D4F, + 0x1D6B00001D78, + 0x1D7900001D9B, + 0x1DC000001E00, + 0x1E0100001E02, + 0x1E0300001E04, + 0x1E0500001E06, + 0x1E0700001E08, + 0x1E0900001E0A, + 0x1E0B00001E0C, + 0x1E0D00001E0E, + 0x1E0F00001E10, + 0x1E1100001E12, + 0x1E1300001E14, + 0x1E1500001E16, + 0x1E1700001E18, + 0x1E1900001E1A, + 0x1E1B00001E1C, + 0x1E1D00001E1E, + 0x1E1F00001E20, + 0x1E2100001E22, + 0x1E2300001E24, + 0x1E2500001E26, + 0x1E2700001E28, + 0x1E2900001E2A, + 0x1E2B00001E2C, + 0x1E2D00001E2E, + 0x1E2F00001E30, + 0x1E3100001E32, + 0x1E3300001E34, + 0x1E3500001E36, + 0x1E3700001E38, + 0x1E3900001E3A, + 0x1E3B00001E3C, + 0x1E3D00001E3E, + 0x1E3F00001E40, + 0x1E4100001E42, + 0x1E4300001E44, + 0x1E4500001E46, + 0x1E4700001E48, + 0x1E4900001E4A, + 0x1E4B00001E4C, + 0x1E4D00001E4E, + 0x1E4F00001E50, + 0x1E5100001E52, + 0x1E5300001E54, + 0x1E5500001E56, + 0x1E5700001E58, + 0x1E5900001E5A, + 0x1E5B00001E5C, + 0x1E5D00001E5E, + 0x1E5F00001E60, + 0x1E6100001E62, + 0x1E6300001E64, + 0x1E6500001E66, + 0x1E6700001E68, + 0x1E6900001E6A, + 0x1E6B00001E6C, + 0x1E6D00001E6E, + 0x1E6F00001E70, + 0x1E7100001E72, + 0x1E7300001E74, + 0x1E7500001E76, + 0x1E7700001E78, + 0x1E7900001E7A, + 0x1E7B00001E7C, + 0x1E7D00001E7E, + 0x1E7F00001E80, + 0x1E8100001E82, + 0x1E8300001E84, + 0x1E8500001E86, + 0x1E8700001E88, + 0x1E8900001E8A, + 0x1E8B00001E8C, + 0x1E8D00001E8E, + 0x1E8F00001E90, + 0x1E9100001E92, + 0x1E9300001E94, + 0x1E9500001E9A, + 0x1E9C00001E9E, + 0x1E9F00001EA0, + 0x1EA100001EA2, + 0x1EA300001EA4, + 0x1EA500001EA6, + 0x1EA700001EA8, + 0x1EA900001EAA, + 0x1EAB00001EAC, + 0x1EAD00001EAE, + 0x1EAF00001EB0, + 0x1EB100001EB2, + 0x1EB300001EB4, + 0x1EB500001EB6, + 0x1EB700001EB8, + 0x1EB900001EBA, + 0x1EBB00001EBC, + 0x1EBD00001EBE, + 0x1EBF00001EC0, + 0x1EC100001EC2, + 0x1EC300001EC4, + 0x1EC500001EC6, + 0x1EC700001EC8, + 0x1EC900001ECA, + 0x1ECB00001ECC, + 0x1ECD00001ECE, + 0x1ECF00001ED0, + 0x1ED100001ED2, + 0x1ED300001ED4, + 0x1ED500001ED6, + 0x1ED700001ED8, + 0x1ED900001EDA, + 0x1EDB00001EDC, + 0x1EDD00001EDE, + 0x1EDF00001EE0, + 0x1EE100001EE2, + 0x1EE300001EE4, + 0x1EE500001EE6, + 0x1EE700001EE8, + 0x1EE900001EEA, + 0x1EEB00001EEC, + 0x1EED00001EEE, + 0x1EEF00001EF0, + 0x1EF100001EF2, + 0x1EF300001EF4, + 0x1EF500001EF6, + 0x1EF700001EF8, + 0x1EF900001EFA, + 0x1EFB00001EFC, + 0x1EFD00001EFE, + 0x1EFF00001F08, + 0x1F1000001F16, + 0x1F2000001F28, + 0x1F3000001F38, + 0x1F4000001F46, + 0x1F5000001F58, + 0x1F6000001F68, + 0x1F7000001F71, + 0x1F7200001F73, + 0x1F7400001F75, + 0x1F7600001F77, + 0x1F7800001F79, + 0x1F7A00001F7B, + 0x1F7C00001F7D, + 0x1FB000001FB2, + 0x1FB600001FB7, + 0x1FC600001FC7, + 0x1FD000001FD3, + 0x1FD600001FD8, + 0x1FE000001FE3, + 0x1FE400001FE8, + 0x1FF600001FF7, + 0x214E0000214F, + 0x218400002185, + 0x2C3000002C60, + 0x2C6100002C62, + 0x2C6500002C67, + 0x2C6800002C69, + 0x2C6A00002C6B, + 0x2C6C00002C6D, + 0x2C7100002C72, + 0x2C7300002C75, + 0x2C7600002C7C, + 0x2C8100002C82, + 0x2C8300002C84, + 0x2C8500002C86, + 0x2C8700002C88, + 0x2C8900002C8A, + 0x2C8B00002C8C, + 0x2C8D00002C8E, + 0x2C8F00002C90, + 0x2C9100002C92, + 0x2C9300002C94, + 0x2C9500002C96, + 0x2C9700002C98, + 0x2C9900002C9A, + 0x2C9B00002C9C, + 0x2C9D00002C9E, + 0x2C9F00002CA0, + 0x2CA100002CA2, + 0x2CA300002CA4, + 0x2CA500002CA6, + 0x2CA700002CA8, + 0x2CA900002CAA, + 0x2CAB00002CAC, + 0x2CAD00002CAE, + 0x2CAF00002CB0, + 0x2CB100002CB2, + 0x2CB300002CB4, + 0x2CB500002CB6, + 0x2CB700002CB8, + 0x2CB900002CBA, + 0x2CBB00002CBC, + 0x2CBD00002CBE, + 0x2CBF00002CC0, + 0x2CC100002CC2, + 0x2CC300002CC4, + 0x2CC500002CC6, + 0x2CC700002CC8, + 0x2CC900002CCA, + 0x2CCB00002CCC, + 0x2CCD00002CCE, + 0x2CCF00002CD0, + 0x2CD100002CD2, + 0x2CD300002CD4, + 0x2CD500002CD6, + 0x2CD700002CD8, + 0x2CD900002CDA, + 0x2CDB00002CDC, + 0x2CDD00002CDE, + 0x2CDF00002CE0, + 0x2CE100002CE2, + 0x2CE300002CE5, + 0x2CEC00002CED, + 0x2CEE00002CF2, + 0x2CF300002CF4, + 0x2D0000002D26, + 0x2D2700002D28, + 0x2D2D00002D2E, + 0x2D3000002D68, + 0x2D7F00002D97, + 0x2DA000002DA7, + 0x2DA800002DAF, + 0x2DB000002DB7, + 0x2DB800002DBF, + 0x2DC000002DC7, + 0x2DC800002DCF, + 0x2DD000002DD7, + 0x2DD800002DDF, + 0x2DE000002E00, + 0x2E2F00002E30, + 0x300500003008, + 0x302A0000302E, + 0x303C0000303D, + 0x304100003097, + 0x30990000309B, + 0x309D0000309F, + 0x30A1000030FB, + 0x30FC000030FF, + 0x310500003130, + 0x31A0000031C0, + 0x31F000003200, + 0x340000004DC0, + 0x4E000000A48D, + 0xA4D00000A4FE, + 0xA5000000A60D, + 0xA6100000A62C, + 0xA6410000A642, + 0xA6430000A644, + 0xA6450000A646, + 0xA6470000A648, + 0xA6490000A64A, + 0xA64B0000A64C, + 0xA64D0000A64E, + 0xA64F0000A650, + 0xA6510000A652, + 0xA6530000A654, + 0xA6550000A656, + 0xA6570000A658, + 0xA6590000A65A, + 0xA65B0000A65C, + 0xA65D0000A65E, + 0xA65F0000A660, + 0xA6610000A662, + 0xA6630000A664, + 0xA6650000A666, + 0xA6670000A668, + 0xA6690000A66A, + 0xA66B0000A66C, + 0xA66D0000A670, + 0xA6740000A67E, + 0xA67F0000A680, + 0xA6810000A682, + 0xA6830000A684, + 0xA6850000A686, + 0xA6870000A688, + 0xA6890000A68A, + 0xA68B0000A68C, + 0xA68D0000A68E, + 0xA68F0000A690, + 0xA6910000A692, + 0xA6930000A694, + 0xA6950000A696, + 0xA6970000A698, + 0xA6990000A69A, + 0xA69B0000A69C, + 0xA69E0000A6E6, + 0xA6F00000A6F2, + 0xA7170000A720, + 0xA7230000A724, + 0xA7250000A726, + 0xA7270000A728, + 0xA7290000A72A, + 0xA72B0000A72C, + 0xA72D0000A72E, + 0xA72F0000A732, + 0xA7330000A734, + 0xA7350000A736, + 0xA7370000A738, + 0xA7390000A73A, + 0xA73B0000A73C, + 0xA73D0000A73E, + 0xA73F0000A740, + 0xA7410000A742, + 0xA7430000A744, + 0xA7450000A746, + 0xA7470000A748, + 0xA7490000A74A, + 0xA74B0000A74C, + 0xA74D0000A74E, + 0xA74F0000A750, + 0xA7510000A752, + 0xA7530000A754, + 0xA7550000A756, + 0xA7570000A758, + 0xA7590000A75A, + 0xA75B0000A75C, + 0xA75D0000A75E, + 0xA75F0000A760, + 0xA7610000A762, + 0xA7630000A764, + 0xA7650000A766, + 0xA7670000A768, + 0xA7690000A76A, + 0xA76B0000A76C, + 0xA76D0000A76E, + 0xA76F0000A770, + 0xA7710000A779, + 0xA77A0000A77B, + 0xA77C0000A77D, + 0xA77F0000A780, + 0xA7810000A782, + 0xA7830000A784, + 0xA7850000A786, + 0xA7870000A789, + 0xA78C0000A78D, + 0xA78E0000A790, + 0xA7910000A792, + 0xA7930000A796, + 0xA7970000A798, + 0xA7990000A79A, + 0xA79B0000A79C, + 0xA79D0000A79E, + 0xA79F0000A7A0, + 0xA7A10000A7A2, + 0xA7A30000A7A4, + 0xA7A50000A7A6, + 0xA7A70000A7A8, + 0xA7A90000A7AA, + 0xA7AF0000A7B0, + 0xA7B50000A7B6, + 0xA7B70000A7B8, + 0xA7B90000A7BA, + 0xA7BB0000A7BC, + 0xA7BD0000A7BE, + 0xA7BF0000A7C0, + 0xA7C10000A7C2, + 0xA7C30000A7C4, + 0xA7C80000A7C9, + 0xA7CA0000A7CB, + 0xA7D10000A7D2, + 0xA7D30000A7D4, + 0xA7D50000A7D6, + 0xA7D70000A7D8, + 0xA7D90000A7DA, + 0xA7F60000A7F8, + 0xA7FA0000A828, + 0xA82C0000A82D, + 0xA8400000A874, + 0xA8800000A8C6, + 0xA8D00000A8DA, + 0xA8E00000A8F8, + 0xA8FB0000A8FC, + 0xA8FD0000A92E, + 0xA9300000A954, + 0xA9800000A9C1, + 0xA9CF0000A9DA, + 0xA9E00000A9FF, + 0xAA000000AA37, + 0xAA400000AA4E, + 0xAA500000AA5A, + 0xAA600000AA77, + 0xAA7A0000AAC3, + 0xAADB0000AADE, + 0xAAE00000AAF0, + 0xAAF20000AAF7, + 0xAB010000AB07, + 0xAB090000AB0F, + 0xAB110000AB17, + 0xAB200000AB27, + 0xAB280000AB2F, + 0xAB300000AB5B, + 0xAB600000AB69, + 0xABC00000ABEB, + 0xABEC0000ABEE, + 0xABF00000ABFA, + 0xAC000000D7A4, + 0xFA0E0000FA10, + 0xFA110000FA12, + 0xFA130000FA15, + 0xFA1F0000FA20, + 0xFA210000FA22, + 0xFA230000FA25, + 0xFA270000FA2A, + 0xFB1E0000FB1F, + 0xFE200000FE30, + 0xFE730000FE74, + 0x100000001000C, + 0x1000D00010027, + 0x100280001003B, + 0x1003C0001003E, + 0x1003F0001004E, + 0x100500001005E, + 0x10080000100FB, + 0x101FD000101FE, + 0x102800001029D, + 0x102A0000102D1, + 0x102E0000102E1, + 0x1030000010320, + 0x1032D00010341, + 0x103420001034A, + 0x103500001037B, + 0x103800001039E, + 0x103A0000103C4, + 0x103C8000103D0, + 0x104280001049E, + 0x104A0000104AA, + 0x104D8000104FC, + 0x1050000010528, + 0x1053000010564, + 0x10597000105A2, + 0x105A3000105B2, + 0x105B3000105BA, + 0x105BB000105BD, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1078000010781, + 0x1080000010806, + 0x1080800010809, + 0x1080A00010836, + 0x1083700010839, + 0x1083C0001083D, + 0x1083F00010856, + 0x1086000010877, + 0x108800001089F, + 0x108E0000108F3, + 0x108F4000108F6, + 0x1090000010916, + 0x109200001093A, + 0x10980000109B8, + 0x109BE000109C0, + 0x10A0000010A04, + 0x10A0500010A07, + 0x10A0C00010A14, + 0x10A1500010A18, + 0x10A1900010A36, + 0x10A3800010A3B, + 0x10A3F00010A40, + 0x10A6000010A7D, + 0x10A8000010A9D, + 0x10AC000010AC8, + 0x10AC900010AE7, + 0x10B0000010B36, + 0x10B4000010B56, + 0x10B6000010B73, + 0x10B8000010B92, + 0x10C0000010C49, + 0x10CC000010CF3, + 0x10D0000010D28, + 0x10D3000010D3A, + 0x10E8000010EAA, + 0x10EAB00010EAD, + 0x10EB000010EB2, + 0x10EFD00010F1D, + 0x10F2700010F28, + 0x10F3000010F51, + 0x10F7000010F86, + 0x10FB000010FC5, + 0x10FE000010FF7, + 0x1100000011047, + 0x1106600011076, + 0x1107F000110BB, + 0x110C2000110C3, + 0x110D0000110E9, + 0x110F0000110FA, + 0x1110000011135, + 0x1113600011140, + 0x1114400011148, + 0x1115000011174, + 0x1117600011177, + 0x11180000111C5, + 0x111C9000111CD, + 0x111CE000111DB, + 0x111DC000111DD, + 0x1120000011212, + 0x1121300011238, + 0x1123E00011242, + 0x1128000011287, + 0x1128800011289, + 0x1128A0001128E, + 0x1128F0001129E, + 0x1129F000112A9, + 0x112B0000112EB, + 0x112F0000112FA, + 0x1130000011304, + 0x113050001130D, + 0x1130F00011311, + 0x1131300011329, + 0x1132A00011331, + 0x1133200011334, + 0x113350001133A, + 0x1133B00011345, + 0x1134700011349, + 0x1134B0001134E, + 0x1135000011351, + 0x1135700011358, + 0x1135D00011364, + 0x113660001136D, + 0x1137000011375, + 0x114000001144B, + 0x114500001145A, + 0x1145E00011462, + 0x11480000114C6, + 0x114C7000114C8, + 0x114D0000114DA, + 0x11580000115B6, + 0x115B8000115C1, + 0x115D8000115DE, + 0x1160000011641, + 0x1164400011645, + 0x116500001165A, + 0x11680000116B9, + 0x116C0000116CA, + 0x117000001171B, + 0x1171D0001172C, + 0x117300001173A, + 0x1174000011747, + 0x118000001183B, + 0x118C0000118EA, + 0x118FF00011907, + 0x119090001190A, + 0x1190C00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193B00011944, + 0x119500001195A, + 0x119A0000119A8, + 0x119AA000119D8, + 0x119DA000119E2, + 0x119E3000119E5, + 0x11A0000011A3F, + 0x11A4700011A48, + 0x11A5000011A9A, + 0x11A9D00011A9E, + 0x11AB000011AF9, + 0x11C0000011C09, + 0x11C0A00011C37, + 0x11C3800011C41, + 0x11C5000011C5A, + 0x11C7200011C90, + 0x11C9200011CA8, + 0x11CA900011CB7, + 0x11D0000011D07, + 0x11D0800011D0A, + 0x11D0B00011D37, + 0x11D3A00011D3B, + 0x11D3C00011D3E, + 0x11D3F00011D48, + 0x11D5000011D5A, + 0x11D6000011D66, + 0x11D6700011D69, + 0x11D6A00011D8F, + 0x11D9000011D92, + 0x11D9300011D99, + 0x11DA000011DAA, + 0x11EE000011EF7, + 0x11F0000011F11, + 0x11F1200011F3B, + 0x11F3E00011F43, + 0x11F5000011F5A, + 0x11FB000011FB1, + 0x120000001239A, + 0x1248000012544, + 0x12F9000012FF1, + 0x1300000013430, + 0x1344000013456, + 0x1440000014647, + 0x1680000016A39, + 0x16A4000016A5F, + 0x16A6000016A6A, + 0x16A7000016ABF, + 0x16AC000016ACA, + 0x16AD000016AEE, + 0x16AF000016AF5, + 0x16B0000016B37, + 0x16B4000016B44, + 0x16B5000016B5A, + 0x16B6300016B78, + 0x16B7D00016B90, + 0x16E6000016E80, + 0x16F0000016F4B, + 0x16F4F00016F88, + 0x16F8F00016FA0, + 0x16FE000016FE2, + 0x16FE300016FE5, + 0x16FF000016FF2, + 0x17000000187F8, + 0x1880000018CD6, + 0x18D0000018D09, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B123, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1B1550001B156, + 0x1B1640001B168, + 0x1B1700001B2FC, + 0x1BC000001BC6B, + 0x1BC700001BC7D, + 0x1BC800001BC89, + 0x1BC900001BC9A, + 0x1BC9D0001BC9F, + 0x1CF000001CF2E, + 0x1CF300001CF47, + 0x1DA000001DA37, + 0x1DA3B0001DA6D, + 0x1DA750001DA76, + 0x1DA840001DA85, + 0x1DA9B0001DAA0, + 0x1DAA10001DAB0, + 0x1DF000001DF1F, + 0x1DF250001DF2B, + 0x1E0000001E007, + 0x1E0080001E019, + 0x1E01B0001E022, + 0x1E0230001E025, + 0x1E0260001E02B, + 0x1E08F0001E090, + 0x1E1000001E12D, + 0x1E1300001E13E, + 0x1E1400001E14A, + 0x1E14E0001E14F, + 0x1E2900001E2AF, + 0x1E2C00001E2FA, + 0x1E4D00001E4FA, + 0x1E7E00001E7E7, + 0x1E7E80001E7EC, + 0x1E7ED0001E7EF, + 0x1E7F00001E7FF, + 0x1E8000001E8C5, + 0x1E8D00001E8D7, + 0x1E9220001E94C, + 0x1E9500001E95A, + 0x200000002A6E0, + 0x2A7000002B73A, + 0x2B7400002B81E, + 0x2B8200002CEA2, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x300000003134B, + 0x31350000323B0, + ), + "CONTEXTJ": (0x200C0000200E,), + "CONTEXTO": ( + 0xB7000000B8, + 0x37500000376, + 0x5F3000005F5, + 0x6600000066A, + 0x6F0000006FA, + 0x30FB000030FC, + ), +} diff --git a/.venv/Lib/site-packages/idna/intranges.py b/.venv/Lib/site-packages/idna/intranges.py new file mode 100644 index 0000000..7bfaa8d --- /dev/null +++ b/.venv/Lib/site-packages/idna/intranges.py @@ -0,0 +1,57 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect +from typing import List, Tuple + + +def intranges_from_list(list_: List[int]) -> Tuple[int, ...]: + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i + 1 < len(sorted_list): + if sorted_list[i] == sorted_list[i + 1] - 1: + continue + current_range = sorted_list[last_write + 1 : i + 1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + + +def _encode_range(start: int, end: int) -> int: + return (start << 32) | end + + +def _decode_range(r: int) -> Tuple[int, int]: + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool: + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos - 1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/.venv/Lib/site-packages/idna/package_data.py b/.venv/Lib/site-packages/idna/package_data.py new file mode 100644 index 0000000..514ff7e --- /dev/null +++ b/.venv/Lib/site-packages/idna/package_data.py @@ -0,0 +1 @@ +__version__ = "3.10" diff --git a/.venv/Lib/site-packages/idna/py.typed b/.venv/Lib/site-packages/idna/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/idna/uts46data.py b/.venv/Lib/site-packages/idna/uts46data.py new file mode 100644 index 0000000..eb89432 --- /dev/null +++ b/.venv/Lib/site-packages/idna/uts46data.py @@ -0,0 +1,8681 @@ +# This file is automatically generated by tools/idna-data +# vim: set fileencoding=utf-8 : + +from typing import List, Tuple, Union + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = "15.1.0" + + +def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x0, "3"), + (0x1, "3"), + (0x2, "3"), + (0x3, "3"), + (0x4, "3"), + (0x5, "3"), + (0x6, "3"), + (0x7, "3"), + (0x8, "3"), + (0x9, "3"), + (0xA, "3"), + (0xB, "3"), + (0xC, "3"), + (0xD, "3"), + (0xE, "3"), + (0xF, "3"), + (0x10, "3"), + (0x11, "3"), + (0x12, "3"), + (0x13, "3"), + (0x14, "3"), + (0x15, "3"), + (0x16, "3"), + (0x17, "3"), + (0x18, "3"), + (0x19, "3"), + (0x1A, "3"), + (0x1B, "3"), + (0x1C, "3"), + (0x1D, "3"), + (0x1E, "3"), + (0x1F, "3"), + (0x20, "3"), + (0x21, "3"), + (0x22, "3"), + (0x23, "3"), + (0x24, "3"), + (0x25, "3"), + (0x26, "3"), + (0x27, "3"), + (0x28, "3"), + (0x29, "3"), + (0x2A, "3"), + (0x2B, "3"), + (0x2C, "3"), + (0x2D, "V"), + (0x2E, "V"), + (0x2F, "3"), + (0x30, "V"), + (0x31, "V"), + (0x32, "V"), + (0x33, "V"), + (0x34, "V"), + (0x35, "V"), + (0x36, "V"), + (0x37, "V"), + (0x38, "V"), + (0x39, "V"), + (0x3A, "3"), + (0x3B, "3"), + (0x3C, "3"), + (0x3D, "3"), + (0x3E, "3"), + (0x3F, "3"), + (0x40, "3"), + (0x41, "M", "a"), + (0x42, "M", "b"), + (0x43, "M", "c"), + (0x44, "M", "d"), + (0x45, "M", "e"), + (0x46, "M", "f"), + (0x47, "M", "g"), + (0x48, "M", "h"), + (0x49, "M", "i"), + (0x4A, "M", "j"), + (0x4B, "M", "k"), + (0x4C, "M", "l"), + (0x4D, "M", "m"), + (0x4E, "M", "n"), + (0x4F, "M", "o"), + (0x50, "M", "p"), + (0x51, "M", "q"), + (0x52, "M", "r"), + (0x53, "M", "s"), + (0x54, "M", "t"), + (0x55, "M", "u"), + (0x56, "M", "v"), + (0x57, "M", "w"), + (0x58, "M", "x"), + (0x59, "M", "y"), + (0x5A, "M", "z"), + (0x5B, "3"), + (0x5C, "3"), + (0x5D, "3"), + (0x5E, "3"), + (0x5F, "3"), + (0x60, "3"), + (0x61, "V"), + (0x62, "V"), + (0x63, "V"), + ] + + +def _seg_1() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x64, "V"), + (0x65, "V"), + (0x66, "V"), + (0x67, "V"), + (0x68, "V"), + (0x69, "V"), + (0x6A, "V"), + (0x6B, "V"), + (0x6C, "V"), + (0x6D, "V"), + (0x6E, "V"), + (0x6F, "V"), + (0x70, "V"), + (0x71, "V"), + (0x72, "V"), + (0x73, "V"), + (0x74, "V"), + (0x75, "V"), + (0x76, "V"), + (0x77, "V"), + (0x78, "V"), + (0x79, "V"), + (0x7A, "V"), + (0x7B, "3"), + (0x7C, "3"), + (0x7D, "3"), + (0x7E, "3"), + (0x7F, "3"), + (0x80, "X"), + (0x81, "X"), + (0x82, "X"), + (0x83, "X"), + (0x84, "X"), + (0x85, "X"), + (0x86, "X"), + (0x87, "X"), + (0x88, "X"), + (0x89, "X"), + (0x8A, "X"), + (0x8B, "X"), + (0x8C, "X"), + (0x8D, "X"), + (0x8E, "X"), + (0x8F, "X"), + (0x90, "X"), + (0x91, "X"), + (0x92, "X"), + (0x93, "X"), + (0x94, "X"), + (0x95, "X"), + (0x96, "X"), + (0x97, "X"), + (0x98, "X"), + (0x99, "X"), + (0x9A, "X"), + (0x9B, "X"), + (0x9C, "X"), + (0x9D, "X"), + (0x9E, "X"), + (0x9F, "X"), + (0xA0, "3", " "), + (0xA1, "V"), + (0xA2, "V"), + (0xA3, "V"), + (0xA4, "V"), + (0xA5, "V"), + (0xA6, "V"), + (0xA7, "V"), + (0xA8, "3", " ̈"), + (0xA9, "V"), + (0xAA, "M", "a"), + (0xAB, "V"), + (0xAC, "V"), + (0xAD, "I"), + (0xAE, "V"), + (0xAF, "3", " ̄"), + (0xB0, "V"), + (0xB1, "V"), + (0xB2, "M", "2"), + (0xB3, "M", "3"), + (0xB4, "3", " ́"), + (0xB5, "M", "μ"), + (0xB6, "V"), + (0xB7, "V"), + (0xB8, "3", " ̧"), + (0xB9, "M", "1"), + (0xBA, "M", "o"), + (0xBB, "V"), + (0xBC, "M", "1⁄4"), + (0xBD, "M", "1⁄2"), + (0xBE, "M", "3⁄4"), + (0xBF, "V"), + (0xC0, "M", "à"), + (0xC1, "M", "á"), + (0xC2, "M", "â"), + (0xC3, "M", "ã"), + (0xC4, "M", "ä"), + (0xC5, "M", "å"), + (0xC6, "M", "æ"), + (0xC7, "M", "ç"), + ] + + +def _seg_2() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xC8, "M", "è"), + (0xC9, "M", "é"), + (0xCA, "M", "ê"), + (0xCB, "M", "ë"), + (0xCC, "M", "ì"), + (0xCD, "M", "í"), + (0xCE, "M", "î"), + (0xCF, "M", "ï"), + (0xD0, "M", "ð"), + (0xD1, "M", "ñ"), + (0xD2, "M", "ò"), + (0xD3, "M", "ó"), + (0xD4, "M", "ô"), + (0xD5, "M", "õ"), + (0xD6, "M", "ö"), + (0xD7, "V"), + (0xD8, "M", "ø"), + (0xD9, "M", "ù"), + (0xDA, "M", "ú"), + (0xDB, "M", "û"), + (0xDC, "M", "ü"), + (0xDD, "M", "ý"), + (0xDE, "M", "þ"), + (0xDF, "D", "ss"), + (0xE0, "V"), + (0xE1, "V"), + (0xE2, "V"), + (0xE3, "V"), + (0xE4, "V"), + (0xE5, "V"), + (0xE6, "V"), + (0xE7, "V"), + (0xE8, "V"), + (0xE9, "V"), + (0xEA, "V"), + (0xEB, "V"), + (0xEC, "V"), + (0xED, "V"), + (0xEE, "V"), + (0xEF, "V"), + (0xF0, "V"), + (0xF1, "V"), + (0xF2, "V"), + (0xF3, "V"), + (0xF4, "V"), + (0xF5, "V"), + (0xF6, "V"), + (0xF7, "V"), + (0xF8, "V"), + (0xF9, "V"), + (0xFA, "V"), + (0xFB, "V"), + (0xFC, "V"), + (0xFD, "V"), + (0xFE, "V"), + (0xFF, "V"), + (0x100, "M", "ā"), + (0x101, "V"), + (0x102, "M", "ă"), + (0x103, "V"), + (0x104, "M", "ą"), + (0x105, "V"), + (0x106, "M", "ć"), + (0x107, "V"), + (0x108, "M", "ĉ"), + (0x109, "V"), + (0x10A, "M", "ċ"), + (0x10B, "V"), + (0x10C, "M", "č"), + (0x10D, "V"), + (0x10E, "M", "ď"), + (0x10F, "V"), + (0x110, "M", "đ"), + (0x111, "V"), + (0x112, "M", "ē"), + (0x113, "V"), + (0x114, "M", "ĕ"), + (0x115, "V"), + (0x116, "M", "ė"), + (0x117, "V"), + (0x118, "M", "ę"), + (0x119, "V"), + (0x11A, "M", "ě"), + (0x11B, "V"), + (0x11C, "M", "ĝ"), + (0x11D, "V"), + (0x11E, "M", "ğ"), + (0x11F, "V"), + (0x120, "M", "ġ"), + (0x121, "V"), + (0x122, "M", "ģ"), + (0x123, "V"), + (0x124, "M", "ĥ"), + (0x125, "V"), + (0x126, "M", "ħ"), + (0x127, "V"), + (0x128, "M", "ĩ"), + (0x129, "V"), + (0x12A, "M", "ī"), + (0x12B, "V"), + ] + + +def _seg_3() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x12C, "M", "ĭ"), + (0x12D, "V"), + (0x12E, "M", "į"), + (0x12F, "V"), + (0x130, "M", "i̇"), + (0x131, "V"), + (0x132, "M", "ij"), + (0x134, "M", "ĵ"), + (0x135, "V"), + (0x136, "M", "ķ"), + (0x137, "V"), + (0x139, "M", "ĺ"), + (0x13A, "V"), + (0x13B, "M", "ļ"), + (0x13C, "V"), + (0x13D, "M", "ľ"), + (0x13E, "V"), + (0x13F, "M", "l·"), + (0x141, "M", "ł"), + (0x142, "V"), + (0x143, "M", "ń"), + (0x144, "V"), + (0x145, "M", "ņ"), + (0x146, "V"), + (0x147, "M", "ň"), + (0x148, "V"), + (0x149, "M", "ʼn"), + (0x14A, "M", "ŋ"), + (0x14B, "V"), + (0x14C, "M", "ō"), + (0x14D, "V"), + (0x14E, "M", "ŏ"), + (0x14F, "V"), + (0x150, "M", "ő"), + (0x151, "V"), + (0x152, "M", "œ"), + (0x153, "V"), + (0x154, "M", "ŕ"), + (0x155, "V"), + (0x156, "M", "ŗ"), + (0x157, "V"), + (0x158, "M", "ř"), + (0x159, "V"), + (0x15A, "M", "ś"), + (0x15B, "V"), + (0x15C, "M", "ŝ"), + (0x15D, "V"), + (0x15E, "M", "ş"), + (0x15F, "V"), + (0x160, "M", "š"), + (0x161, "V"), + (0x162, "M", "ţ"), + (0x163, "V"), + (0x164, "M", "ť"), + (0x165, "V"), + (0x166, "M", "ŧ"), + (0x167, "V"), + (0x168, "M", "ũ"), + (0x169, "V"), + (0x16A, "M", "ū"), + (0x16B, "V"), + (0x16C, "M", "ŭ"), + (0x16D, "V"), + (0x16E, "M", "ů"), + (0x16F, "V"), + (0x170, "M", "ű"), + (0x171, "V"), + (0x172, "M", "ų"), + (0x173, "V"), + (0x174, "M", "ŵ"), + (0x175, "V"), + (0x176, "M", "ŷ"), + (0x177, "V"), + (0x178, "M", "ÿ"), + (0x179, "M", "ź"), + (0x17A, "V"), + (0x17B, "M", "ż"), + (0x17C, "V"), + (0x17D, "M", "ž"), + (0x17E, "V"), + (0x17F, "M", "s"), + (0x180, "V"), + (0x181, "M", "ɓ"), + (0x182, "M", "ƃ"), + (0x183, "V"), + (0x184, "M", "ƅ"), + (0x185, "V"), + (0x186, "M", "ɔ"), + (0x187, "M", "ƈ"), + (0x188, "V"), + (0x189, "M", "ɖ"), + (0x18A, "M", "ɗ"), + (0x18B, "M", "ƌ"), + (0x18C, "V"), + (0x18E, "M", "ǝ"), + (0x18F, "M", "ə"), + (0x190, "M", "ɛ"), + (0x191, "M", "ƒ"), + (0x192, "V"), + (0x193, "M", "ɠ"), + ] + + +def _seg_4() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x194, "M", "ɣ"), + (0x195, "V"), + (0x196, "M", "ɩ"), + (0x197, "M", "ɨ"), + (0x198, "M", "ƙ"), + (0x199, "V"), + (0x19C, "M", "ɯ"), + (0x19D, "M", "ɲ"), + (0x19E, "V"), + (0x19F, "M", "ɵ"), + (0x1A0, "M", "ơ"), + (0x1A1, "V"), + (0x1A2, "M", "ƣ"), + (0x1A3, "V"), + (0x1A4, "M", "ƥ"), + (0x1A5, "V"), + (0x1A6, "M", "ʀ"), + (0x1A7, "M", "ƨ"), + (0x1A8, "V"), + (0x1A9, "M", "ʃ"), + (0x1AA, "V"), + (0x1AC, "M", "ƭ"), + (0x1AD, "V"), + (0x1AE, "M", "ʈ"), + (0x1AF, "M", "ư"), + (0x1B0, "V"), + (0x1B1, "M", "ʊ"), + (0x1B2, "M", "ʋ"), + (0x1B3, "M", "ƴ"), + (0x1B4, "V"), + (0x1B5, "M", "ƶ"), + (0x1B6, "V"), + (0x1B7, "M", "ʒ"), + (0x1B8, "M", "ƹ"), + (0x1B9, "V"), + (0x1BC, "M", "ƽ"), + (0x1BD, "V"), + (0x1C4, "M", "dž"), + (0x1C7, "M", "lj"), + (0x1CA, "M", "nj"), + (0x1CD, "M", "ǎ"), + (0x1CE, "V"), + (0x1CF, "M", "ǐ"), + (0x1D0, "V"), + (0x1D1, "M", "ǒ"), + (0x1D2, "V"), + (0x1D3, "M", "ǔ"), + (0x1D4, "V"), + (0x1D5, "M", "ǖ"), + (0x1D6, "V"), + (0x1D7, "M", "ǘ"), + (0x1D8, "V"), + (0x1D9, "M", "ǚ"), + (0x1DA, "V"), + (0x1DB, "M", "ǜ"), + (0x1DC, "V"), + (0x1DE, "M", "ǟ"), + (0x1DF, "V"), + (0x1E0, "M", "ǡ"), + (0x1E1, "V"), + (0x1E2, "M", "ǣ"), + (0x1E3, "V"), + (0x1E4, "M", "ǥ"), + (0x1E5, "V"), + (0x1E6, "M", "ǧ"), + (0x1E7, "V"), + (0x1E8, "M", "ǩ"), + (0x1E9, "V"), + (0x1EA, "M", "ǫ"), + (0x1EB, "V"), + (0x1EC, "M", "ǭ"), + (0x1ED, "V"), + (0x1EE, "M", "ǯ"), + (0x1EF, "V"), + (0x1F1, "M", "dz"), + (0x1F4, "M", "ǵ"), + (0x1F5, "V"), + (0x1F6, "M", "ƕ"), + (0x1F7, "M", "ƿ"), + (0x1F8, "M", "ǹ"), + (0x1F9, "V"), + (0x1FA, "M", "ǻ"), + (0x1FB, "V"), + (0x1FC, "M", "ǽ"), + (0x1FD, "V"), + (0x1FE, "M", "ǿ"), + (0x1FF, "V"), + (0x200, "M", "ȁ"), + (0x201, "V"), + (0x202, "M", "ȃ"), + (0x203, "V"), + (0x204, "M", "ȅ"), + (0x205, "V"), + (0x206, "M", "ȇ"), + (0x207, "V"), + (0x208, "M", "ȉ"), + (0x209, "V"), + (0x20A, "M", "ȋ"), + (0x20B, "V"), + (0x20C, "M", "ȍ"), + ] + + +def _seg_5() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x20D, "V"), + (0x20E, "M", "ȏ"), + (0x20F, "V"), + (0x210, "M", "ȑ"), + (0x211, "V"), + (0x212, "M", "ȓ"), + (0x213, "V"), + (0x214, "M", "ȕ"), + (0x215, "V"), + (0x216, "M", "ȗ"), + (0x217, "V"), + (0x218, "M", "ș"), + (0x219, "V"), + (0x21A, "M", "ț"), + (0x21B, "V"), + (0x21C, "M", "ȝ"), + (0x21D, "V"), + (0x21E, "M", "ȟ"), + (0x21F, "V"), + (0x220, "M", "ƞ"), + (0x221, "V"), + (0x222, "M", "ȣ"), + (0x223, "V"), + (0x224, "M", "ȥ"), + (0x225, "V"), + (0x226, "M", "ȧ"), + (0x227, "V"), + (0x228, "M", "ȩ"), + (0x229, "V"), + (0x22A, "M", "ȫ"), + (0x22B, "V"), + (0x22C, "M", "ȭ"), + (0x22D, "V"), + (0x22E, "M", "ȯ"), + (0x22F, "V"), + (0x230, "M", "ȱ"), + (0x231, "V"), + (0x232, "M", "ȳ"), + (0x233, "V"), + (0x23A, "M", "ⱥ"), + (0x23B, "M", "ȼ"), + (0x23C, "V"), + (0x23D, "M", "ƚ"), + (0x23E, "M", "ⱦ"), + (0x23F, "V"), + (0x241, "M", "ɂ"), + (0x242, "V"), + (0x243, "M", "ƀ"), + (0x244, "M", "ʉ"), + (0x245, "M", "ʌ"), + (0x246, "M", "ɇ"), + (0x247, "V"), + (0x248, "M", "ɉ"), + (0x249, "V"), + (0x24A, "M", "ɋ"), + (0x24B, "V"), + (0x24C, "M", "ɍ"), + (0x24D, "V"), + (0x24E, "M", "ɏ"), + (0x24F, "V"), + (0x2B0, "M", "h"), + (0x2B1, "M", "ɦ"), + (0x2B2, "M", "j"), + (0x2B3, "M", "r"), + (0x2B4, "M", "ɹ"), + (0x2B5, "M", "ɻ"), + (0x2B6, "M", "ʁ"), + (0x2B7, "M", "w"), + (0x2B8, "M", "y"), + (0x2B9, "V"), + (0x2D8, "3", " ̆"), + (0x2D9, "3", " ̇"), + (0x2DA, "3", " ̊"), + (0x2DB, "3", " ̨"), + (0x2DC, "3", " ̃"), + (0x2DD, "3", " ̋"), + (0x2DE, "V"), + (0x2E0, "M", "ɣ"), + (0x2E1, "M", "l"), + (0x2E2, "M", "s"), + (0x2E3, "M", "x"), + (0x2E4, "M", "ʕ"), + (0x2E5, "V"), + (0x340, "M", "̀"), + (0x341, "M", "́"), + (0x342, "V"), + (0x343, "M", "̓"), + (0x344, "M", "̈́"), + (0x345, "M", "ι"), + (0x346, "V"), + (0x34F, "I"), + (0x350, "V"), + (0x370, "M", "ͱ"), + (0x371, "V"), + (0x372, "M", "ͳ"), + (0x373, "V"), + (0x374, "M", "ʹ"), + (0x375, "V"), + (0x376, "M", "ͷ"), + (0x377, "V"), + ] + + +def _seg_6() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x378, "X"), + (0x37A, "3", " ι"), + (0x37B, "V"), + (0x37E, "3", ";"), + (0x37F, "M", "ϳ"), + (0x380, "X"), + (0x384, "3", " ́"), + (0x385, "3", " ̈́"), + (0x386, "M", "ά"), + (0x387, "M", "·"), + (0x388, "M", "έ"), + (0x389, "M", "ή"), + (0x38A, "M", "ί"), + (0x38B, "X"), + (0x38C, "M", "ό"), + (0x38D, "X"), + (0x38E, "M", "ύ"), + (0x38F, "M", "ώ"), + (0x390, "V"), + (0x391, "M", "α"), + (0x392, "M", "β"), + (0x393, "M", "γ"), + (0x394, "M", "δ"), + (0x395, "M", "ε"), + (0x396, "M", "ζ"), + (0x397, "M", "η"), + (0x398, "M", "θ"), + (0x399, "M", "ι"), + (0x39A, "M", "κ"), + (0x39B, "M", "λ"), + (0x39C, "M", "μ"), + (0x39D, "M", "ν"), + (0x39E, "M", "ξ"), + (0x39F, "M", "ο"), + (0x3A0, "M", "π"), + (0x3A1, "M", "ρ"), + (0x3A2, "X"), + (0x3A3, "M", "σ"), + (0x3A4, "M", "τ"), + (0x3A5, "M", "υ"), + (0x3A6, "M", "φ"), + (0x3A7, "M", "χ"), + (0x3A8, "M", "ψ"), + (0x3A9, "M", "ω"), + (0x3AA, "M", "ϊ"), + (0x3AB, "M", "ϋ"), + (0x3AC, "V"), + (0x3C2, "D", "σ"), + (0x3C3, "V"), + (0x3CF, "M", "ϗ"), + (0x3D0, "M", "β"), + (0x3D1, "M", "θ"), + (0x3D2, "M", "υ"), + (0x3D3, "M", "ύ"), + (0x3D4, "M", "ϋ"), + (0x3D5, "M", "φ"), + (0x3D6, "M", "π"), + (0x3D7, "V"), + (0x3D8, "M", "ϙ"), + (0x3D9, "V"), + (0x3DA, "M", "ϛ"), + (0x3DB, "V"), + (0x3DC, "M", "ϝ"), + (0x3DD, "V"), + (0x3DE, "M", "ϟ"), + (0x3DF, "V"), + (0x3E0, "M", "ϡ"), + (0x3E1, "V"), + (0x3E2, "M", "ϣ"), + (0x3E3, "V"), + (0x3E4, "M", "ϥ"), + (0x3E5, "V"), + (0x3E6, "M", "ϧ"), + (0x3E7, "V"), + (0x3E8, "M", "ϩ"), + (0x3E9, "V"), + (0x3EA, "M", "ϫ"), + (0x3EB, "V"), + (0x3EC, "M", "ϭ"), + (0x3ED, "V"), + (0x3EE, "M", "ϯ"), + (0x3EF, "V"), + (0x3F0, "M", "κ"), + (0x3F1, "M", "ρ"), + (0x3F2, "M", "σ"), + (0x3F3, "V"), + (0x3F4, "M", "θ"), + (0x3F5, "M", "ε"), + (0x3F6, "V"), + (0x3F7, "M", "ϸ"), + (0x3F8, "V"), + (0x3F9, "M", "σ"), + (0x3FA, "M", "ϻ"), + (0x3FB, "V"), + (0x3FD, "M", "ͻ"), + (0x3FE, "M", "ͼ"), + (0x3FF, "M", "ͽ"), + (0x400, "M", "ѐ"), + (0x401, "M", "ё"), + (0x402, "M", "ђ"), + ] + + +def _seg_7() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x403, "M", "ѓ"), + (0x404, "M", "є"), + (0x405, "M", "ѕ"), + (0x406, "M", "і"), + (0x407, "M", "ї"), + (0x408, "M", "ј"), + (0x409, "M", "љ"), + (0x40A, "M", "њ"), + (0x40B, "M", "ћ"), + (0x40C, "M", "ќ"), + (0x40D, "M", "ѝ"), + (0x40E, "M", "ў"), + (0x40F, "M", "џ"), + (0x410, "M", "а"), + (0x411, "M", "б"), + (0x412, "M", "в"), + (0x413, "M", "г"), + (0x414, "M", "д"), + (0x415, "M", "е"), + (0x416, "M", "ж"), + (0x417, "M", "з"), + (0x418, "M", "и"), + (0x419, "M", "й"), + (0x41A, "M", "к"), + (0x41B, "M", "л"), + (0x41C, "M", "м"), + (0x41D, "M", "н"), + (0x41E, "M", "о"), + (0x41F, "M", "п"), + (0x420, "M", "р"), + (0x421, "M", "с"), + (0x422, "M", "т"), + (0x423, "M", "у"), + (0x424, "M", "ф"), + (0x425, "M", "х"), + (0x426, "M", "ц"), + (0x427, "M", "ч"), + (0x428, "M", "ш"), + (0x429, "M", "щ"), + (0x42A, "M", "ъ"), + (0x42B, "M", "ы"), + (0x42C, "M", "ь"), + (0x42D, "M", "э"), + (0x42E, "M", "ю"), + (0x42F, "M", "я"), + (0x430, "V"), + (0x460, "M", "ѡ"), + (0x461, "V"), + (0x462, "M", "ѣ"), + (0x463, "V"), + (0x464, "M", "ѥ"), + (0x465, "V"), + (0x466, "M", "ѧ"), + (0x467, "V"), + (0x468, "M", "ѩ"), + (0x469, "V"), + (0x46A, "M", "ѫ"), + (0x46B, "V"), + (0x46C, "M", "ѭ"), + (0x46D, "V"), + (0x46E, "M", "ѯ"), + (0x46F, "V"), + (0x470, "M", "ѱ"), + (0x471, "V"), + (0x472, "M", "ѳ"), + (0x473, "V"), + (0x474, "M", "ѵ"), + (0x475, "V"), + (0x476, "M", "ѷ"), + (0x477, "V"), + (0x478, "M", "ѹ"), + (0x479, "V"), + (0x47A, "M", "ѻ"), + (0x47B, "V"), + (0x47C, "M", "ѽ"), + (0x47D, "V"), + (0x47E, "M", "ѿ"), + (0x47F, "V"), + (0x480, "M", "ҁ"), + (0x481, "V"), + (0x48A, "M", "ҋ"), + (0x48B, "V"), + (0x48C, "M", "ҍ"), + (0x48D, "V"), + (0x48E, "M", "ҏ"), + (0x48F, "V"), + (0x490, "M", "ґ"), + (0x491, "V"), + (0x492, "M", "ғ"), + (0x493, "V"), + (0x494, "M", "ҕ"), + (0x495, "V"), + (0x496, "M", "җ"), + (0x497, "V"), + (0x498, "M", "ҙ"), + (0x499, "V"), + (0x49A, "M", "қ"), + (0x49B, "V"), + (0x49C, "M", "ҝ"), + (0x49D, "V"), + ] + + +def _seg_8() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x49E, "M", "ҟ"), + (0x49F, "V"), + (0x4A0, "M", "ҡ"), + (0x4A1, "V"), + (0x4A2, "M", "ң"), + (0x4A3, "V"), + (0x4A4, "M", "ҥ"), + (0x4A5, "V"), + (0x4A6, "M", "ҧ"), + (0x4A7, "V"), + (0x4A8, "M", "ҩ"), + (0x4A9, "V"), + (0x4AA, "M", "ҫ"), + (0x4AB, "V"), + (0x4AC, "M", "ҭ"), + (0x4AD, "V"), + (0x4AE, "M", "ү"), + (0x4AF, "V"), + (0x4B0, "M", "ұ"), + (0x4B1, "V"), + (0x4B2, "M", "ҳ"), + (0x4B3, "V"), + (0x4B4, "M", "ҵ"), + (0x4B5, "V"), + (0x4B6, "M", "ҷ"), + (0x4B7, "V"), + (0x4B8, "M", "ҹ"), + (0x4B9, "V"), + (0x4BA, "M", "һ"), + (0x4BB, "V"), + (0x4BC, "M", "ҽ"), + (0x4BD, "V"), + (0x4BE, "M", "ҿ"), + (0x4BF, "V"), + (0x4C0, "X"), + (0x4C1, "M", "ӂ"), + (0x4C2, "V"), + (0x4C3, "M", "ӄ"), + (0x4C4, "V"), + (0x4C5, "M", "ӆ"), + (0x4C6, "V"), + (0x4C7, "M", "ӈ"), + (0x4C8, "V"), + (0x4C9, "M", "ӊ"), + (0x4CA, "V"), + (0x4CB, "M", "ӌ"), + (0x4CC, "V"), + (0x4CD, "M", "ӎ"), + (0x4CE, "V"), + (0x4D0, "M", "ӑ"), + (0x4D1, "V"), + (0x4D2, "M", "ӓ"), + (0x4D3, "V"), + (0x4D4, "M", "ӕ"), + (0x4D5, "V"), + (0x4D6, "M", "ӗ"), + (0x4D7, "V"), + (0x4D8, "M", "ә"), + (0x4D9, "V"), + (0x4DA, "M", "ӛ"), + (0x4DB, "V"), + (0x4DC, "M", "ӝ"), + (0x4DD, "V"), + (0x4DE, "M", "ӟ"), + (0x4DF, "V"), + (0x4E0, "M", "ӡ"), + (0x4E1, "V"), + (0x4E2, "M", "ӣ"), + (0x4E3, "V"), + (0x4E4, "M", "ӥ"), + (0x4E5, "V"), + (0x4E6, "M", "ӧ"), + (0x4E7, "V"), + (0x4E8, "M", "ө"), + (0x4E9, "V"), + (0x4EA, "M", "ӫ"), + (0x4EB, "V"), + (0x4EC, "M", "ӭ"), + (0x4ED, "V"), + (0x4EE, "M", "ӯ"), + (0x4EF, "V"), + (0x4F0, "M", "ӱ"), + (0x4F1, "V"), + (0x4F2, "M", "ӳ"), + (0x4F3, "V"), + (0x4F4, "M", "ӵ"), + (0x4F5, "V"), + (0x4F6, "M", "ӷ"), + (0x4F7, "V"), + (0x4F8, "M", "ӹ"), + (0x4F9, "V"), + (0x4FA, "M", "ӻ"), + (0x4FB, "V"), + (0x4FC, "M", "ӽ"), + (0x4FD, "V"), + (0x4FE, "M", "ӿ"), + (0x4FF, "V"), + (0x500, "M", "ԁ"), + (0x501, "V"), + (0x502, "M", "ԃ"), + ] + + +def _seg_9() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x503, "V"), + (0x504, "M", "ԅ"), + (0x505, "V"), + (0x506, "M", "ԇ"), + (0x507, "V"), + (0x508, "M", "ԉ"), + (0x509, "V"), + (0x50A, "M", "ԋ"), + (0x50B, "V"), + (0x50C, "M", "ԍ"), + (0x50D, "V"), + (0x50E, "M", "ԏ"), + (0x50F, "V"), + (0x510, "M", "ԑ"), + (0x511, "V"), + (0x512, "M", "ԓ"), + (0x513, "V"), + (0x514, "M", "ԕ"), + (0x515, "V"), + (0x516, "M", "ԗ"), + (0x517, "V"), + (0x518, "M", "ԙ"), + (0x519, "V"), + (0x51A, "M", "ԛ"), + (0x51B, "V"), + (0x51C, "M", "ԝ"), + (0x51D, "V"), + (0x51E, "M", "ԟ"), + (0x51F, "V"), + (0x520, "M", "ԡ"), + (0x521, "V"), + (0x522, "M", "ԣ"), + (0x523, "V"), + (0x524, "M", "ԥ"), + (0x525, "V"), + (0x526, "M", "ԧ"), + (0x527, "V"), + (0x528, "M", "ԩ"), + (0x529, "V"), + (0x52A, "M", "ԫ"), + (0x52B, "V"), + (0x52C, "M", "ԭ"), + (0x52D, "V"), + (0x52E, "M", "ԯ"), + (0x52F, "V"), + (0x530, "X"), + (0x531, "M", "ա"), + (0x532, "M", "բ"), + (0x533, "M", "գ"), + (0x534, "M", "դ"), + (0x535, "M", "ե"), + (0x536, "M", "զ"), + (0x537, "M", "է"), + (0x538, "M", "ը"), + (0x539, "M", "թ"), + (0x53A, "M", "ժ"), + (0x53B, "M", "ի"), + (0x53C, "M", "լ"), + (0x53D, "M", "խ"), + (0x53E, "M", "ծ"), + (0x53F, "M", "կ"), + (0x540, "M", "հ"), + (0x541, "M", "ձ"), + (0x542, "M", "ղ"), + (0x543, "M", "ճ"), + (0x544, "M", "մ"), + (0x545, "M", "յ"), + (0x546, "M", "ն"), + (0x547, "M", "շ"), + (0x548, "M", "ո"), + (0x549, "M", "չ"), + (0x54A, "M", "պ"), + (0x54B, "M", "ջ"), + (0x54C, "M", "ռ"), + (0x54D, "M", "ս"), + (0x54E, "M", "վ"), + (0x54F, "M", "տ"), + (0x550, "M", "ր"), + (0x551, "M", "ց"), + (0x552, "M", "ւ"), + (0x553, "M", "փ"), + (0x554, "M", "ք"), + (0x555, "M", "օ"), + (0x556, "M", "ֆ"), + (0x557, "X"), + (0x559, "V"), + (0x587, "M", "եւ"), + (0x588, "V"), + (0x58B, "X"), + (0x58D, "V"), + (0x590, "X"), + (0x591, "V"), + (0x5C8, "X"), + (0x5D0, "V"), + (0x5EB, "X"), + (0x5EF, "V"), + (0x5F5, "X"), + (0x606, "V"), + (0x61C, "X"), + (0x61D, "V"), + ] + + +def _seg_10() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x675, "M", "اٴ"), + (0x676, "M", "وٴ"), + (0x677, "M", "ۇٴ"), + (0x678, "M", "يٴ"), + (0x679, "V"), + (0x6DD, "X"), + (0x6DE, "V"), + (0x70E, "X"), + (0x710, "V"), + (0x74B, "X"), + (0x74D, "V"), + (0x7B2, "X"), + (0x7C0, "V"), + (0x7FB, "X"), + (0x7FD, "V"), + (0x82E, "X"), + (0x830, "V"), + (0x83F, "X"), + (0x840, "V"), + (0x85C, "X"), + (0x85E, "V"), + (0x85F, "X"), + (0x860, "V"), + (0x86B, "X"), + (0x870, "V"), + (0x88F, "X"), + (0x898, "V"), + (0x8E2, "X"), + (0x8E3, "V"), + (0x958, "M", "क़"), + (0x959, "M", "ख़"), + (0x95A, "M", "ग़"), + (0x95B, "M", "ज़"), + (0x95C, "M", "ड़"), + (0x95D, "M", "ढ़"), + (0x95E, "M", "फ़"), + (0x95F, "M", "य़"), + (0x960, "V"), + (0x984, "X"), + (0x985, "V"), + (0x98D, "X"), + (0x98F, "V"), + (0x991, "X"), + (0x993, "V"), + (0x9A9, "X"), + (0x9AA, "V"), + (0x9B1, "X"), + (0x9B2, "V"), + (0x9B3, "X"), + (0x9B6, "V"), + (0x9BA, "X"), + (0x9BC, "V"), + (0x9C5, "X"), + (0x9C7, "V"), + (0x9C9, "X"), + (0x9CB, "V"), + (0x9CF, "X"), + (0x9D7, "V"), + (0x9D8, "X"), + (0x9DC, "M", "ড়"), + (0x9DD, "M", "ঢ়"), + (0x9DE, "X"), + (0x9DF, "M", "য়"), + (0x9E0, "V"), + (0x9E4, "X"), + (0x9E6, "V"), + (0x9FF, "X"), + (0xA01, "V"), + (0xA04, "X"), + (0xA05, "V"), + (0xA0B, "X"), + (0xA0F, "V"), + (0xA11, "X"), + (0xA13, "V"), + (0xA29, "X"), + (0xA2A, "V"), + (0xA31, "X"), + (0xA32, "V"), + (0xA33, "M", "ਲ਼"), + (0xA34, "X"), + (0xA35, "V"), + (0xA36, "M", "ਸ਼"), + (0xA37, "X"), + (0xA38, "V"), + (0xA3A, "X"), + (0xA3C, "V"), + (0xA3D, "X"), + (0xA3E, "V"), + (0xA43, "X"), + (0xA47, "V"), + (0xA49, "X"), + (0xA4B, "V"), + (0xA4E, "X"), + (0xA51, "V"), + (0xA52, "X"), + (0xA59, "M", "ਖ਼"), + (0xA5A, "M", "ਗ਼"), + (0xA5B, "M", "ਜ਼"), + (0xA5C, "V"), + (0xA5D, "X"), + ] + + +def _seg_11() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA5E, "M", "ਫ਼"), + (0xA5F, "X"), + (0xA66, "V"), + (0xA77, "X"), + (0xA81, "V"), + (0xA84, "X"), + (0xA85, "V"), + (0xA8E, "X"), + (0xA8F, "V"), + (0xA92, "X"), + (0xA93, "V"), + (0xAA9, "X"), + (0xAAA, "V"), + (0xAB1, "X"), + (0xAB2, "V"), + (0xAB4, "X"), + (0xAB5, "V"), + (0xABA, "X"), + (0xABC, "V"), + (0xAC6, "X"), + (0xAC7, "V"), + (0xACA, "X"), + (0xACB, "V"), + (0xACE, "X"), + (0xAD0, "V"), + (0xAD1, "X"), + (0xAE0, "V"), + (0xAE4, "X"), + (0xAE6, "V"), + (0xAF2, "X"), + (0xAF9, "V"), + (0xB00, "X"), + (0xB01, "V"), + (0xB04, "X"), + (0xB05, "V"), + (0xB0D, "X"), + (0xB0F, "V"), + (0xB11, "X"), + (0xB13, "V"), + (0xB29, "X"), + (0xB2A, "V"), + (0xB31, "X"), + (0xB32, "V"), + (0xB34, "X"), + (0xB35, "V"), + (0xB3A, "X"), + (0xB3C, "V"), + (0xB45, "X"), + (0xB47, "V"), + (0xB49, "X"), + (0xB4B, "V"), + (0xB4E, "X"), + (0xB55, "V"), + (0xB58, "X"), + (0xB5C, "M", "ଡ଼"), + (0xB5D, "M", "ଢ଼"), + (0xB5E, "X"), + (0xB5F, "V"), + (0xB64, "X"), + (0xB66, "V"), + (0xB78, "X"), + (0xB82, "V"), + (0xB84, "X"), + (0xB85, "V"), + (0xB8B, "X"), + (0xB8E, "V"), + (0xB91, "X"), + (0xB92, "V"), + (0xB96, "X"), + (0xB99, "V"), + (0xB9B, "X"), + (0xB9C, "V"), + (0xB9D, "X"), + (0xB9E, "V"), + (0xBA0, "X"), + (0xBA3, "V"), + (0xBA5, "X"), + (0xBA8, "V"), + (0xBAB, "X"), + (0xBAE, "V"), + (0xBBA, "X"), + (0xBBE, "V"), + (0xBC3, "X"), + (0xBC6, "V"), + (0xBC9, "X"), + (0xBCA, "V"), + (0xBCE, "X"), + (0xBD0, "V"), + (0xBD1, "X"), + (0xBD7, "V"), + (0xBD8, "X"), + (0xBE6, "V"), + (0xBFB, "X"), + (0xC00, "V"), + (0xC0D, "X"), + (0xC0E, "V"), + (0xC11, "X"), + (0xC12, "V"), + (0xC29, "X"), + (0xC2A, "V"), + ] + + +def _seg_12() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xC3A, "X"), + (0xC3C, "V"), + (0xC45, "X"), + (0xC46, "V"), + (0xC49, "X"), + (0xC4A, "V"), + (0xC4E, "X"), + (0xC55, "V"), + (0xC57, "X"), + (0xC58, "V"), + (0xC5B, "X"), + (0xC5D, "V"), + (0xC5E, "X"), + (0xC60, "V"), + (0xC64, "X"), + (0xC66, "V"), + (0xC70, "X"), + (0xC77, "V"), + (0xC8D, "X"), + (0xC8E, "V"), + (0xC91, "X"), + (0xC92, "V"), + (0xCA9, "X"), + (0xCAA, "V"), + (0xCB4, "X"), + (0xCB5, "V"), + (0xCBA, "X"), + (0xCBC, "V"), + (0xCC5, "X"), + (0xCC6, "V"), + (0xCC9, "X"), + (0xCCA, "V"), + (0xCCE, "X"), + (0xCD5, "V"), + (0xCD7, "X"), + (0xCDD, "V"), + (0xCDF, "X"), + (0xCE0, "V"), + (0xCE4, "X"), + (0xCE6, "V"), + (0xCF0, "X"), + (0xCF1, "V"), + (0xCF4, "X"), + (0xD00, "V"), + (0xD0D, "X"), + (0xD0E, "V"), + (0xD11, "X"), + (0xD12, "V"), + (0xD45, "X"), + (0xD46, "V"), + (0xD49, "X"), + (0xD4A, "V"), + (0xD50, "X"), + (0xD54, "V"), + (0xD64, "X"), + (0xD66, "V"), + (0xD80, "X"), + (0xD81, "V"), + (0xD84, "X"), + (0xD85, "V"), + (0xD97, "X"), + (0xD9A, "V"), + (0xDB2, "X"), + (0xDB3, "V"), + (0xDBC, "X"), + (0xDBD, "V"), + (0xDBE, "X"), + (0xDC0, "V"), + (0xDC7, "X"), + (0xDCA, "V"), + (0xDCB, "X"), + (0xDCF, "V"), + (0xDD5, "X"), + (0xDD6, "V"), + (0xDD7, "X"), + (0xDD8, "V"), + (0xDE0, "X"), + (0xDE6, "V"), + (0xDF0, "X"), + (0xDF2, "V"), + (0xDF5, "X"), + (0xE01, "V"), + (0xE33, "M", "ํา"), + (0xE34, "V"), + (0xE3B, "X"), + (0xE3F, "V"), + (0xE5C, "X"), + (0xE81, "V"), + (0xE83, "X"), + (0xE84, "V"), + (0xE85, "X"), + (0xE86, "V"), + (0xE8B, "X"), + (0xE8C, "V"), + (0xEA4, "X"), + (0xEA5, "V"), + (0xEA6, "X"), + (0xEA7, "V"), + (0xEB3, "M", "ໍາ"), + (0xEB4, "V"), + ] + + +def _seg_13() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xEBE, "X"), + (0xEC0, "V"), + (0xEC5, "X"), + (0xEC6, "V"), + (0xEC7, "X"), + (0xEC8, "V"), + (0xECF, "X"), + (0xED0, "V"), + (0xEDA, "X"), + (0xEDC, "M", "ຫນ"), + (0xEDD, "M", "ຫມ"), + (0xEDE, "V"), + (0xEE0, "X"), + (0xF00, "V"), + (0xF0C, "M", "་"), + (0xF0D, "V"), + (0xF43, "M", "གྷ"), + (0xF44, "V"), + (0xF48, "X"), + (0xF49, "V"), + (0xF4D, "M", "ཌྷ"), + (0xF4E, "V"), + (0xF52, "M", "དྷ"), + (0xF53, "V"), + (0xF57, "M", "བྷ"), + (0xF58, "V"), + (0xF5C, "M", "ཛྷ"), + (0xF5D, "V"), + (0xF69, "M", "ཀྵ"), + (0xF6A, "V"), + (0xF6D, "X"), + (0xF71, "V"), + (0xF73, "M", "ཱི"), + (0xF74, "V"), + (0xF75, "M", "ཱུ"), + (0xF76, "M", "ྲྀ"), + (0xF77, "M", "ྲཱྀ"), + (0xF78, "M", "ླྀ"), + (0xF79, "M", "ླཱྀ"), + (0xF7A, "V"), + (0xF81, "M", "ཱྀ"), + (0xF82, "V"), + (0xF93, "M", "ྒྷ"), + (0xF94, "V"), + (0xF98, "X"), + (0xF99, "V"), + (0xF9D, "M", "ྜྷ"), + (0xF9E, "V"), + (0xFA2, "M", "ྡྷ"), + (0xFA3, "V"), + (0xFA7, "M", "ྦྷ"), + (0xFA8, "V"), + (0xFAC, "M", "ྫྷ"), + (0xFAD, "V"), + (0xFB9, "M", "ྐྵ"), + (0xFBA, "V"), + (0xFBD, "X"), + (0xFBE, "V"), + (0xFCD, "X"), + (0xFCE, "V"), + (0xFDB, "X"), + (0x1000, "V"), + (0x10A0, "X"), + (0x10C7, "M", "ⴧ"), + (0x10C8, "X"), + (0x10CD, "M", "ⴭ"), + (0x10CE, "X"), + (0x10D0, "V"), + (0x10FC, "M", "ნ"), + (0x10FD, "V"), + (0x115F, "X"), + (0x1161, "V"), + (0x1249, "X"), + (0x124A, "V"), + (0x124E, "X"), + (0x1250, "V"), + (0x1257, "X"), + (0x1258, "V"), + (0x1259, "X"), + (0x125A, "V"), + (0x125E, "X"), + (0x1260, "V"), + (0x1289, "X"), + (0x128A, "V"), + (0x128E, "X"), + (0x1290, "V"), + (0x12B1, "X"), + (0x12B2, "V"), + (0x12B6, "X"), + (0x12B8, "V"), + (0x12BF, "X"), + (0x12C0, "V"), + (0x12C1, "X"), + (0x12C2, "V"), + (0x12C6, "X"), + (0x12C8, "V"), + (0x12D7, "X"), + (0x12D8, "V"), + (0x1311, "X"), + (0x1312, "V"), + ] + + +def _seg_14() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1316, "X"), + (0x1318, "V"), + (0x135B, "X"), + (0x135D, "V"), + (0x137D, "X"), + (0x1380, "V"), + (0x139A, "X"), + (0x13A0, "V"), + (0x13F6, "X"), + (0x13F8, "M", "Ᏸ"), + (0x13F9, "M", "Ᏹ"), + (0x13FA, "M", "Ᏺ"), + (0x13FB, "M", "Ᏻ"), + (0x13FC, "M", "Ᏼ"), + (0x13FD, "M", "Ᏽ"), + (0x13FE, "X"), + (0x1400, "V"), + (0x1680, "X"), + (0x1681, "V"), + (0x169D, "X"), + (0x16A0, "V"), + (0x16F9, "X"), + (0x1700, "V"), + (0x1716, "X"), + (0x171F, "V"), + (0x1737, "X"), + (0x1740, "V"), + (0x1754, "X"), + (0x1760, "V"), + (0x176D, "X"), + (0x176E, "V"), + (0x1771, "X"), + (0x1772, "V"), + (0x1774, "X"), + (0x1780, "V"), + (0x17B4, "X"), + (0x17B6, "V"), + (0x17DE, "X"), + (0x17E0, "V"), + (0x17EA, "X"), + (0x17F0, "V"), + (0x17FA, "X"), + (0x1800, "V"), + (0x1806, "X"), + (0x1807, "V"), + (0x180B, "I"), + (0x180E, "X"), + (0x180F, "I"), + (0x1810, "V"), + (0x181A, "X"), + (0x1820, "V"), + (0x1879, "X"), + (0x1880, "V"), + (0x18AB, "X"), + (0x18B0, "V"), + (0x18F6, "X"), + (0x1900, "V"), + (0x191F, "X"), + (0x1920, "V"), + (0x192C, "X"), + (0x1930, "V"), + (0x193C, "X"), + (0x1940, "V"), + (0x1941, "X"), + (0x1944, "V"), + (0x196E, "X"), + (0x1970, "V"), + (0x1975, "X"), + (0x1980, "V"), + (0x19AC, "X"), + (0x19B0, "V"), + (0x19CA, "X"), + (0x19D0, "V"), + (0x19DB, "X"), + (0x19DE, "V"), + (0x1A1C, "X"), + (0x1A1E, "V"), + (0x1A5F, "X"), + (0x1A60, "V"), + (0x1A7D, "X"), + (0x1A7F, "V"), + (0x1A8A, "X"), + (0x1A90, "V"), + (0x1A9A, "X"), + (0x1AA0, "V"), + (0x1AAE, "X"), + (0x1AB0, "V"), + (0x1ACF, "X"), + (0x1B00, "V"), + (0x1B4D, "X"), + (0x1B50, "V"), + (0x1B7F, "X"), + (0x1B80, "V"), + (0x1BF4, "X"), + (0x1BFC, "V"), + (0x1C38, "X"), + (0x1C3B, "V"), + (0x1C4A, "X"), + (0x1C4D, "V"), + (0x1C80, "M", "в"), + ] + + +def _seg_15() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1C81, "M", "д"), + (0x1C82, "M", "о"), + (0x1C83, "M", "с"), + (0x1C84, "M", "т"), + (0x1C86, "M", "ъ"), + (0x1C87, "M", "ѣ"), + (0x1C88, "M", "ꙋ"), + (0x1C89, "X"), + (0x1C90, "M", "ა"), + (0x1C91, "M", "ბ"), + (0x1C92, "M", "გ"), + (0x1C93, "M", "დ"), + (0x1C94, "M", "ე"), + (0x1C95, "M", "ვ"), + (0x1C96, "M", "ზ"), + (0x1C97, "M", "თ"), + (0x1C98, "M", "ი"), + (0x1C99, "M", "კ"), + (0x1C9A, "M", "ლ"), + (0x1C9B, "M", "მ"), + (0x1C9C, "M", "ნ"), + (0x1C9D, "M", "ო"), + (0x1C9E, "M", "პ"), + (0x1C9F, "M", "ჟ"), + (0x1CA0, "M", "რ"), + (0x1CA1, "M", "ს"), + (0x1CA2, "M", "ტ"), + (0x1CA3, "M", "უ"), + (0x1CA4, "M", "ფ"), + (0x1CA5, "M", "ქ"), + (0x1CA6, "M", "ღ"), + (0x1CA7, "M", "ყ"), + (0x1CA8, "M", "შ"), + (0x1CA9, "M", "ჩ"), + (0x1CAA, "M", "ც"), + (0x1CAB, "M", "ძ"), + (0x1CAC, "M", "წ"), + (0x1CAD, "M", "ჭ"), + (0x1CAE, "M", "ხ"), + (0x1CAF, "M", "ჯ"), + (0x1CB0, "M", "ჰ"), + (0x1CB1, "M", "ჱ"), + (0x1CB2, "M", "ჲ"), + (0x1CB3, "M", "ჳ"), + (0x1CB4, "M", "ჴ"), + (0x1CB5, "M", "ჵ"), + (0x1CB6, "M", "ჶ"), + (0x1CB7, "M", "ჷ"), + (0x1CB8, "M", "ჸ"), + (0x1CB9, "M", "ჹ"), + (0x1CBA, "M", "ჺ"), + (0x1CBB, "X"), + (0x1CBD, "M", "ჽ"), + (0x1CBE, "M", "ჾ"), + (0x1CBF, "M", "ჿ"), + (0x1CC0, "V"), + (0x1CC8, "X"), + (0x1CD0, "V"), + (0x1CFB, "X"), + (0x1D00, "V"), + (0x1D2C, "M", "a"), + (0x1D2D, "M", "æ"), + (0x1D2E, "M", "b"), + (0x1D2F, "V"), + (0x1D30, "M", "d"), + (0x1D31, "M", "e"), + (0x1D32, "M", "ǝ"), + (0x1D33, "M", "g"), + (0x1D34, "M", "h"), + (0x1D35, "M", "i"), + (0x1D36, "M", "j"), + (0x1D37, "M", "k"), + (0x1D38, "M", "l"), + (0x1D39, "M", "m"), + (0x1D3A, "M", "n"), + (0x1D3B, "V"), + (0x1D3C, "M", "o"), + (0x1D3D, "M", "ȣ"), + (0x1D3E, "M", "p"), + (0x1D3F, "M", "r"), + (0x1D40, "M", "t"), + (0x1D41, "M", "u"), + (0x1D42, "M", "w"), + (0x1D43, "M", "a"), + (0x1D44, "M", "ɐ"), + (0x1D45, "M", "ɑ"), + (0x1D46, "M", "ᴂ"), + (0x1D47, "M", "b"), + (0x1D48, "M", "d"), + (0x1D49, "M", "e"), + (0x1D4A, "M", "ə"), + (0x1D4B, "M", "ɛ"), + (0x1D4C, "M", "ɜ"), + (0x1D4D, "M", "g"), + (0x1D4E, "V"), + (0x1D4F, "M", "k"), + (0x1D50, "M", "m"), + (0x1D51, "M", "ŋ"), + (0x1D52, "M", "o"), + (0x1D53, "M", "ɔ"), + ] + + +def _seg_16() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D54, "M", "ᴖ"), + (0x1D55, "M", "ᴗ"), + (0x1D56, "M", "p"), + (0x1D57, "M", "t"), + (0x1D58, "M", "u"), + (0x1D59, "M", "ᴝ"), + (0x1D5A, "M", "ɯ"), + (0x1D5B, "M", "v"), + (0x1D5C, "M", "ᴥ"), + (0x1D5D, "M", "β"), + (0x1D5E, "M", "γ"), + (0x1D5F, "M", "δ"), + (0x1D60, "M", "φ"), + (0x1D61, "M", "χ"), + (0x1D62, "M", "i"), + (0x1D63, "M", "r"), + (0x1D64, "M", "u"), + (0x1D65, "M", "v"), + (0x1D66, "M", "β"), + (0x1D67, "M", "γ"), + (0x1D68, "M", "ρ"), + (0x1D69, "M", "φ"), + (0x1D6A, "M", "χ"), + (0x1D6B, "V"), + (0x1D78, "M", "н"), + (0x1D79, "V"), + (0x1D9B, "M", "ɒ"), + (0x1D9C, "M", "c"), + (0x1D9D, "M", "ɕ"), + (0x1D9E, "M", "ð"), + (0x1D9F, "M", "ɜ"), + (0x1DA0, "M", "f"), + (0x1DA1, "M", "ɟ"), + (0x1DA2, "M", "ɡ"), + (0x1DA3, "M", "ɥ"), + (0x1DA4, "M", "ɨ"), + (0x1DA5, "M", "ɩ"), + (0x1DA6, "M", "ɪ"), + (0x1DA7, "M", "ᵻ"), + (0x1DA8, "M", "ʝ"), + (0x1DA9, "M", "ɭ"), + (0x1DAA, "M", "ᶅ"), + (0x1DAB, "M", "ʟ"), + (0x1DAC, "M", "ɱ"), + (0x1DAD, "M", "ɰ"), + (0x1DAE, "M", "ɲ"), + (0x1DAF, "M", "ɳ"), + (0x1DB0, "M", "ɴ"), + (0x1DB1, "M", "ɵ"), + (0x1DB2, "M", "ɸ"), + (0x1DB3, "M", "ʂ"), + (0x1DB4, "M", "ʃ"), + (0x1DB5, "M", "ƫ"), + (0x1DB6, "M", "ʉ"), + (0x1DB7, "M", "ʊ"), + (0x1DB8, "M", "ᴜ"), + (0x1DB9, "M", "ʋ"), + (0x1DBA, "M", "ʌ"), + (0x1DBB, "M", "z"), + (0x1DBC, "M", "ʐ"), + (0x1DBD, "M", "ʑ"), + (0x1DBE, "M", "ʒ"), + (0x1DBF, "M", "θ"), + (0x1DC0, "V"), + (0x1E00, "M", "ḁ"), + (0x1E01, "V"), + (0x1E02, "M", "ḃ"), + (0x1E03, "V"), + (0x1E04, "M", "ḅ"), + (0x1E05, "V"), + (0x1E06, "M", "ḇ"), + (0x1E07, "V"), + (0x1E08, "M", "ḉ"), + (0x1E09, "V"), + (0x1E0A, "M", "ḋ"), + (0x1E0B, "V"), + (0x1E0C, "M", "ḍ"), + (0x1E0D, "V"), + (0x1E0E, "M", "ḏ"), + (0x1E0F, "V"), + (0x1E10, "M", "ḑ"), + (0x1E11, "V"), + (0x1E12, "M", "ḓ"), + (0x1E13, "V"), + (0x1E14, "M", "ḕ"), + (0x1E15, "V"), + (0x1E16, "M", "ḗ"), + (0x1E17, "V"), + (0x1E18, "M", "ḙ"), + (0x1E19, "V"), + (0x1E1A, "M", "ḛ"), + (0x1E1B, "V"), + (0x1E1C, "M", "ḝ"), + (0x1E1D, "V"), + (0x1E1E, "M", "ḟ"), + (0x1E1F, "V"), + (0x1E20, "M", "ḡ"), + (0x1E21, "V"), + (0x1E22, "M", "ḣ"), + (0x1E23, "V"), + ] + + +def _seg_17() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E24, "M", "ḥ"), + (0x1E25, "V"), + (0x1E26, "M", "ḧ"), + (0x1E27, "V"), + (0x1E28, "M", "ḩ"), + (0x1E29, "V"), + (0x1E2A, "M", "ḫ"), + (0x1E2B, "V"), + (0x1E2C, "M", "ḭ"), + (0x1E2D, "V"), + (0x1E2E, "M", "ḯ"), + (0x1E2F, "V"), + (0x1E30, "M", "ḱ"), + (0x1E31, "V"), + (0x1E32, "M", "ḳ"), + (0x1E33, "V"), + (0x1E34, "M", "ḵ"), + (0x1E35, "V"), + (0x1E36, "M", "ḷ"), + (0x1E37, "V"), + (0x1E38, "M", "ḹ"), + (0x1E39, "V"), + (0x1E3A, "M", "ḻ"), + (0x1E3B, "V"), + (0x1E3C, "M", "ḽ"), + (0x1E3D, "V"), + (0x1E3E, "M", "ḿ"), + (0x1E3F, "V"), + (0x1E40, "M", "ṁ"), + (0x1E41, "V"), + (0x1E42, "M", "ṃ"), + (0x1E43, "V"), + (0x1E44, "M", "ṅ"), + (0x1E45, "V"), + (0x1E46, "M", "ṇ"), + (0x1E47, "V"), + (0x1E48, "M", "ṉ"), + (0x1E49, "V"), + (0x1E4A, "M", "ṋ"), + (0x1E4B, "V"), + (0x1E4C, "M", "ṍ"), + (0x1E4D, "V"), + (0x1E4E, "M", "ṏ"), + (0x1E4F, "V"), + (0x1E50, "M", "ṑ"), + (0x1E51, "V"), + (0x1E52, "M", "ṓ"), + (0x1E53, "V"), + (0x1E54, "M", "ṕ"), + (0x1E55, "V"), + (0x1E56, "M", "ṗ"), + (0x1E57, "V"), + (0x1E58, "M", "ṙ"), + (0x1E59, "V"), + (0x1E5A, "M", "ṛ"), + (0x1E5B, "V"), + (0x1E5C, "M", "ṝ"), + (0x1E5D, "V"), + (0x1E5E, "M", "ṟ"), + (0x1E5F, "V"), + (0x1E60, "M", "ṡ"), + (0x1E61, "V"), + (0x1E62, "M", "ṣ"), + (0x1E63, "V"), + (0x1E64, "M", "ṥ"), + (0x1E65, "V"), + (0x1E66, "M", "ṧ"), + (0x1E67, "V"), + (0x1E68, "M", "ṩ"), + (0x1E69, "V"), + (0x1E6A, "M", "ṫ"), + (0x1E6B, "V"), + (0x1E6C, "M", "ṭ"), + (0x1E6D, "V"), + (0x1E6E, "M", "ṯ"), + (0x1E6F, "V"), + (0x1E70, "M", "ṱ"), + (0x1E71, "V"), + (0x1E72, "M", "ṳ"), + (0x1E73, "V"), + (0x1E74, "M", "ṵ"), + (0x1E75, "V"), + (0x1E76, "M", "ṷ"), + (0x1E77, "V"), + (0x1E78, "M", "ṹ"), + (0x1E79, "V"), + (0x1E7A, "M", "ṻ"), + (0x1E7B, "V"), + (0x1E7C, "M", "ṽ"), + (0x1E7D, "V"), + (0x1E7E, "M", "ṿ"), + (0x1E7F, "V"), + (0x1E80, "M", "ẁ"), + (0x1E81, "V"), + (0x1E82, "M", "ẃ"), + (0x1E83, "V"), + (0x1E84, "M", "ẅ"), + (0x1E85, "V"), + (0x1E86, "M", "ẇ"), + (0x1E87, "V"), + ] + + +def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E88, "M", "ẉ"), + (0x1E89, "V"), + (0x1E8A, "M", "ẋ"), + (0x1E8B, "V"), + (0x1E8C, "M", "ẍ"), + (0x1E8D, "V"), + (0x1E8E, "M", "ẏ"), + (0x1E8F, "V"), + (0x1E90, "M", "ẑ"), + (0x1E91, "V"), + (0x1E92, "M", "ẓ"), + (0x1E93, "V"), + (0x1E94, "M", "ẕ"), + (0x1E95, "V"), + (0x1E9A, "M", "aʾ"), + (0x1E9B, "M", "ṡ"), + (0x1E9C, "V"), + (0x1E9E, "M", "ß"), + (0x1E9F, "V"), + (0x1EA0, "M", "ạ"), + (0x1EA1, "V"), + (0x1EA2, "M", "ả"), + (0x1EA3, "V"), + (0x1EA4, "M", "ấ"), + (0x1EA5, "V"), + (0x1EA6, "M", "ầ"), + (0x1EA7, "V"), + (0x1EA8, "M", "ẩ"), + (0x1EA9, "V"), + (0x1EAA, "M", "ẫ"), + (0x1EAB, "V"), + (0x1EAC, "M", "ậ"), + (0x1EAD, "V"), + (0x1EAE, "M", "ắ"), + (0x1EAF, "V"), + (0x1EB0, "M", "ằ"), + (0x1EB1, "V"), + (0x1EB2, "M", "ẳ"), + (0x1EB3, "V"), + (0x1EB4, "M", "ẵ"), + (0x1EB5, "V"), + (0x1EB6, "M", "ặ"), + (0x1EB7, "V"), + (0x1EB8, "M", "ẹ"), + (0x1EB9, "V"), + (0x1EBA, "M", "ẻ"), + (0x1EBB, "V"), + (0x1EBC, "M", "ẽ"), + (0x1EBD, "V"), + (0x1EBE, "M", "ế"), + (0x1EBF, "V"), + (0x1EC0, "M", "ề"), + (0x1EC1, "V"), + (0x1EC2, "M", "ể"), + (0x1EC3, "V"), + (0x1EC4, "M", "ễ"), + (0x1EC5, "V"), + (0x1EC6, "M", "ệ"), + (0x1EC7, "V"), + (0x1EC8, "M", "ỉ"), + (0x1EC9, "V"), + (0x1ECA, "M", "ị"), + (0x1ECB, "V"), + (0x1ECC, "M", "ọ"), + (0x1ECD, "V"), + (0x1ECE, "M", "ỏ"), + (0x1ECF, "V"), + (0x1ED0, "M", "ố"), + (0x1ED1, "V"), + (0x1ED2, "M", "ồ"), + (0x1ED3, "V"), + (0x1ED4, "M", "ổ"), + (0x1ED5, "V"), + (0x1ED6, "M", "ỗ"), + (0x1ED7, "V"), + (0x1ED8, "M", "ộ"), + (0x1ED9, "V"), + (0x1EDA, "M", "ớ"), + (0x1EDB, "V"), + (0x1EDC, "M", "ờ"), + (0x1EDD, "V"), + (0x1EDE, "M", "ở"), + (0x1EDF, "V"), + (0x1EE0, "M", "ỡ"), + (0x1EE1, "V"), + (0x1EE2, "M", "ợ"), + (0x1EE3, "V"), + (0x1EE4, "M", "ụ"), + (0x1EE5, "V"), + (0x1EE6, "M", "ủ"), + (0x1EE7, "V"), + (0x1EE8, "M", "ứ"), + (0x1EE9, "V"), + (0x1EEA, "M", "ừ"), + (0x1EEB, "V"), + (0x1EEC, "M", "ử"), + (0x1EED, "V"), + (0x1EEE, "M", "ữ"), + (0x1EEF, "V"), + (0x1EF0, "M", "ự"), + ] + + +def _seg_19() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EF1, "V"), + (0x1EF2, "M", "ỳ"), + (0x1EF3, "V"), + (0x1EF4, "M", "ỵ"), + (0x1EF5, "V"), + (0x1EF6, "M", "ỷ"), + (0x1EF7, "V"), + (0x1EF8, "M", "ỹ"), + (0x1EF9, "V"), + (0x1EFA, "M", "ỻ"), + (0x1EFB, "V"), + (0x1EFC, "M", "ỽ"), + (0x1EFD, "V"), + (0x1EFE, "M", "ỿ"), + (0x1EFF, "V"), + (0x1F08, "M", "ἀ"), + (0x1F09, "M", "ἁ"), + (0x1F0A, "M", "ἂ"), + (0x1F0B, "M", "ἃ"), + (0x1F0C, "M", "ἄ"), + (0x1F0D, "M", "ἅ"), + (0x1F0E, "M", "ἆ"), + (0x1F0F, "M", "ἇ"), + (0x1F10, "V"), + (0x1F16, "X"), + (0x1F18, "M", "ἐ"), + (0x1F19, "M", "ἑ"), + (0x1F1A, "M", "ἒ"), + (0x1F1B, "M", "ἓ"), + (0x1F1C, "M", "ἔ"), + (0x1F1D, "M", "ἕ"), + (0x1F1E, "X"), + (0x1F20, "V"), + (0x1F28, "M", "ἠ"), + (0x1F29, "M", "ἡ"), + (0x1F2A, "M", "ἢ"), + (0x1F2B, "M", "ἣ"), + (0x1F2C, "M", "ἤ"), + (0x1F2D, "M", "ἥ"), + (0x1F2E, "M", "ἦ"), + (0x1F2F, "M", "ἧ"), + (0x1F30, "V"), + (0x1F38, "M", "ἰ"), + (0x1F39, "M", "ἱ"), + (0x1F3A, "M", "ἲ"), + (0x1F3B, "M", "ἳ"), + (0x1F3C, "M", "ἴ"), + (0x1F3D, "M", "ἵ"), + (0x1F3E, "M", "ἶ"), + (0x1F3F, "M", "ἷ"), + (0x1F40, "V"), + (0x1F46, "X"), + (0x1F48, "M", "ὀ"), + (0x1F49, "M", "ὁ"), + (0x1F4A, "M", "ὂ"), + (0x1F4B, "M", "ὃ"), + (0x1F4C, "M", "ὄ"), + (0x1F4D, "M", "ὅ"), + (0x1F4E, "X"), + (0x1F50, "V"), + (0x1F58, "X"), + (0x1F59, "M", "ὑ"), + (0x1F5A, "X"), + (0x1F5B, "M", "ὓ"), + (0x1F5C, "X"), + (0x1F5D, "M", "ὕ"), + (0x1F5E, "X"), + (0x1F5F, "M", "ὗ"), + (0x1F60, "V"), + (0x1F68, "M", "ὠ"), + (0x1F69, "M", "ὡ"), + (0x1F6A, "M", "ὢ"), + (0x1F6B, "M", "ὣ"), + (0x1F6C, "M", "ὤ"), + (0x1F6D, "M", "ὥ"), + (0x1F6E, "M", "ὦ"), + (0x1F6F, "M", "ὧ"), + (0x1F70, "V"), + (0x1F71, "M", "ά"), + (0x1F72, "V"), + (0x1F73, "M", "έ"), + (0x1F74, "V"), + (0x1F75, "M", "ή"), + (0x1F76, "V"), + (0x1F77, "M", "ί"), + (0x1F78, "V"), + (0x1F79, "M", "ό"), + (0x1F7A, "V"), + (0x1F7B, "M", "ύ"), + (0x1F7C, "V"), + (0x1F7D, "M", "ώ"), + (0x1F7E, "X"), + (0x1F80, "M", "ἀι"), + (0x1F81, "M", "ἁι"), + (0x1F82, "M", "ἂι"), + (0x1F83, "M", "ἃι"), + (0x1F84, "M", "ἄι"), + (0x1F85, "M", "ἅι"), + (0x1F86, "M", "ἆι"), + (0x1F87, "M", "ἇι"), + ] + + +def _seg_20() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1F88, "M", "ἀι"), + (0x1F89, "M", "ἁι"), + (0x1F8A, "M", "ἂι"), + (0x1F8B, "M", "ἃι"), + (0x1F8C, "M", "ἄι"), + (0x1F8D, "M", "ἅι"), + (0x1F8E, "M", "ἆι"), + (0x1F8F, "M", "ἇι"), + (0x1F90, "M", "ἠι"), + (0x1F91, "M", "ἡι"), + (0x1F92, "M", "ἢι"), + (0x1F93, "M", "ἣι"), + (0x1F94, "M", "ἤι"), + (0x1F95, "M", "ἥι"), + (0x1F96, "M", "ἦι"), + (0x1F97, "M", "ἧι"), + (0x1F98, "M", "ἠι"), + (0x1F99, "M", "ἡι"), + (0x1F9A, "M", "ἢι"), + (0x1F9B, "M", "ἣι"), + (0x1F9C, "M", "ἤι"), + (0x1F9D, "M", "ἥι"), + (0x1F9E, "M", "ἦι"), + (0x1F9F, "M", "ἧι"), + (0x1FA0, "M", "ὠι"), + (0x1FA1, "M", "ὡι"), + (0x1FA2, "M", "ὢι"), + (0x1FA3, "M", "ὣι"), + (0x1FA4, "M", "ὤι"), + (0x1FA5, "M", "ὥι"), + (0x1FA6, "M", "ὦι"), + (0x1FA7, "M", "ὧι"), + (0x1FA8, "M", "ὠι"), + (0x1FA9, "M", "ὡι"), + (0x1FAA, "M", "ὢι"), + (0x1FAB, "M", "ὣι"), + (0x1FAC, "M", "ὤι"), + (0x1FAD, "M", "ὥι"), + (0x1FAE, "M", "ὦι"), + (0x1FAF, "M", "ὧι"), + (0x1FB0, "V"), + (0x1FB2, "M", "ὰι"), + (0x1FB3, "M", "αι"), + (0x1FB4, "M", "άι"), + (0x1FB5, "X"), + (0x1FB6, "V"), + (0x1FB7, "M", "ᾶι"), + (0x1FB8, "M", "ᾰ"), + (0x1FB9, "M", "ᾱ"), + (0x1FBA, "M", "ὰ"), + (0x1FBB, "M", "ά"), + (0x1FBC, "M", "αι"), + (0x1FBD, "3", " ̓"), + (0x1FBE, "M", "ι"), + (0x1FBF, "3", " ̓"), + (0x1FC0, "3", " ͂"), + (0x1FC1, "3", " ̈͂"), + (0x1FC2, "M", "ὴι"), + (0x1FC3, "M", "ηι"), + (0x1FC4, "M", "ήι"), + (0x1FC5, "X"), + (0x1FC6, "V"), + (0x1FC7, "M", "ῆι"), + (0x1FC8, "M", "ὲ"), + (0x1FC9, "M", "έ"), + (0x1FCA, "M", "ὴ"), + (0x1FCB, "M", "ή"), + (0x1FCC, "M", "ηι"), + (0x1FCD, "3", " ̓̀"), + (0x1FCE, "3", " ̓́"), + (0x1FCF, "3", " ̓͂"), + (0x1FD0, "V"), + (0x1FD3, "M", "ΐ"), + (0x1FD4, "X"), + (0x1FD6, "V"), + (0x1FD8, "M", "ῐ"), + (0x1FD9, "M", "ῑ"), + (0x1FDA, "M", "ὶ"), + (0x1FDB, "M", "ί"), + (0x1FDC, "X"), + (0x1FDD, "3", " ̔̀"), + (0x1FDE, "3", " ̔́"), + (0x1FDF, "3", " ̔͂"), + (0x1FE0, "V"), + (0x1FE3, "M", "ΰ"), + (0x1FE4, "V"), + (0x1FE8, "M", "ῠ"), + (0x1FE9, "M", "ῡ"), + (0x1FEA, "M", "ὺ"), + (0x1FEB, "M", "ύ"), + (0x1FEC, "M", "ῥ"), + (0x1FED, "3", " ̈̀"), + (0x1FEE, "3", " ̈́"), + (0x1FEF, "3", "`"), + (0x1FF0, "X"), + (0x1FF2, "M", "ὼι"), + (0x1FF3, "M", "ωι"), + (0x1FF4, "M", "ώι"), + (0x1FF5, "X"), + (0x1FF6, "V"), + ] + + +def _seg_21() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1FF7, "M", "ῶι"), + (0x1FF8, "M", "ὸ"), + (0x1FF9, "M", "ό"), + (0x1FFA, "M", "ὼ"), + (0x1FFB, "M", "ώ"), + (0x1FFC, "M", "ωι"), + (0x1FFD, "3", " ́"), + (0x1FFE, "3", " ̔"), + (0x1FFF, "X"), + (0x2000, "3", " "), + (0x200B, "I"), + (0x200C, "D", ""), + (0x200E, "X"), + (0x2010, "V"), + (0x2011, "M", "‐"), + (0x2012, "V"), + (0x2017, "3", " ̳"), + (0x2018, "V"), + (0x2024, "X"), + (0x2027, "V"), + (0x2028, "X"), + (0x202F, "3", " "), + (0x2030, "V"), + (0x2033, "M", "′′"), + (0x2034, "M", "′′′"), + (0x2035, "V"), + (0x2036, "M", "‵‵"), + (0x2037, "M", "‵‵‵"), + (0x2038, "V"), + (0x203C, "3", "!!"), + (0x203D, "V"), + (0x203E, "3", " ̅"), + (0x203F, "V"), + (0x2047, "3", "??"), + (0x2048, "3", "?!"), + (0x2049, "3", "!?"), + (0x204A, "V"), + (0x2057, "M", "′′′′"), + (0x2058, "V"), + (0x205F, "3", " "), + (0x2060, "I"), + (0x2061, "X"), + (0x2064, "I"), + (0x2065, "X"), + (0x2070, "M", "0"), + (0x2071, "M", "i"), + (0x2072, "X"), + (0x2074, "M", "4"), + (0x2075, "M", "5"), + (0x2076, "M", "6"), + (0x2077, "M", "7"), + (0x2078, "M", "8"), + (0x2079, "M", "9"), + (0x207A, "3", "+"), + (0x207B, "M", "−"), + (0x207C, "3", "="), + (0x207D, "3", "("), + (0x207E, "3", ")"), + (0x207F, "M", "n"), + (0x2080, "M", "0"), + (0x2081, "M", "1"), + (0x2082, "M", "2"), + (0x2083, "M", "3"), + (0x2084, "M", "4"), + (0x2085, "M", "5"), + (0x2086, "M", "6"), + (0x2087, "M", "7"), + (0x2088, "M", "8"), + (0x2089, "M", "9"), + (0x208A, "3", "+"), + (0x208B, "M", "−"), + (0x208C, "3", "="), + (0x208D, "3", "("), + (0x208E, "3", ")"), + (0x208F, "X"), + (0x2090, "M", "a"), + (0x2091, "M", "e"), + (0x2092, "M", "o"), + (0x2093, "M", "x"), + (0x2094, "M", "ə"), + (0x2095, "M", "h"), + (0x2096, "M", "k"), + (0x2097, "M", "l"), + (0x2098, "M", "m"), + (0x2099, "M", "n"), + (0x209A, "M", "p"), + (0x209B, "M", "s"), + (0x209C, "M", "t"), + (0x209D, "X"), + (0x20A0, "V"), + (0x20A8, "M", "rs"), + (0x20A9, "V"), + (0x20C1, "X"), + (0x20D0, "V"), + (0x20F1, "X"), + (0x2100, "3", "a/c"), + (0x2101, "3", "a/s"), + (0x2102, "M", "c"), + (0x2103, "M", "°c"), + (0x2104, "V"), + ] + + +def _seg_22() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2105, "3", "c/o"), + (0x2106, "3", "c/u"), + (0x2107, "M", "ɛ"), + (0x2108, "V"), + (0x2109, "M", "°f"), + (0x210A, "M", "g"), + (0x210B, "M", "h"), + (0x210F, "M", "ħ"), + (0x2110, "M", "i"), + (0x2112, "M", "l"), + (0x2114, "V"), + (0x2115, "M", "n"), + (0x2116, "M", "no"), + (0x2117, "V"), + (0x2119, "M", "p"), + (0x211A, "M", "q"), + (0x211B, "M", "r"), + (0x211E, "V"), + (0x2120, "M", "sm"), + (0x2121, "M", "tel"), + (0x2122, "M", "tm"), + (0x2123, "V"), + (0x2124, "M", "z"), + (0x2125, "V"), + (0x2126, "M", "ω"), + (0x2127, "V"), + (0x2128, "M", "z"), + (0x2129, "V"), + (0x212A, "M", "k"), + (0x212B, "M", "å"), + (0x212C, "M", "b"), + (0x212D, "M", "c"), + (0x212E, "V"), + (0x212F, "M", "e"), + (0x2131, "M", "f"), + (0x2132, "X"), + (0x2133, "M", "m"), + (0x2134, "M", "o"), + (0x2135, "M", "א"), + (0x2136, "M", "ב"), + (0x2137, "M", "ג"), + (0x2138, "M", "ד"), + (0x2139, "M", "i"), + (0x213A, "V"), + (0x213B, "M", "fax"), + (0x213C, "M", "π"), + (0x213D, "M", "γ"), + (0x213F, "M", "π"), + (0x2140, "M", "∑"), + (0x2141, "V"), + (0x2145, "M", "d"), + (0x2147, "M", "e"), + (0x2148, "M", "i"), + (0x2149, "M", "j"), + (0x214A, "V"), + (0x2150, "M", "1⁄7"), + (0x2151, "M", "1⁄9"), + (0x2152, "M", "1⁄10"), + (0x2153, "M", "1⁄3"), + (0x2154, "M", "2⁄3"), + (0x2155, "M", "1⁄5"), + (0x2156, "M", "2⁄5"), + (0x2157, "M", "3⁄5"), + (0x2158, "M", "4⁄5"), + (0x2159, "M", "1⁄6"), + (0x215A, "M", "5⁄6"), + (0x215B, "M", "1⁄8"), + (0x215C, "M", "3⁄8"), + (0x215D, "M", "5⁄8"), + (0x215E, "M", "7⁄8"), + (0x215F, "M", "1⁄"), + (0x2160, "M", "i"), + (0x2161, "M", "ii"), + (0x2162, "M", "iii"), + (0x2163, "M", "iv"), + (0x2164, "M", "v"), + (0x2165, "M", "vi"), + (0x2166, "M", "vii"), + (0x2167, "M", "viii"), + (0x2168, "M", "ix"), + (0x2169, "M", "x"), + (0x216A, "M", "xi"), + (0x216B, "M", "xii"), + (0x216C, "M", "l"), + (0x216D, "M", "c"), + (0x216E, "M", "d"), + (0x216F, "M", "m"), + (0x2170, "M", "i"), + (0x2171, "M", "ii"), + (0x2172, "M", "iii"), + (0x2173, "M", "iv"), + (0x2174, "M", "v"), + (0x2175, "M", "vi"), + (0x2176, "M", "vii"), + (0x2177, "M", "viii"), + (0x2178, "M", "ix"), + (0x2179, "M", "x"), + (0x217A, "M", "xi"), + (0x217B, "M", "xii"), + (0x217C, "M", "l"), + ] + + +def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x217D, "M", "c"), + (0x217E, "M", "d"), + (0x217F, "M", "m"), + (0x2180, "V"), + (0x2183, "X"), + (0x2184, "V"), + (0x2189, "M", "0⁄3"), + (0x218A, "V"), + (0x218C, "X"), + (0x2190, "V"), + (0x222C, "M", "∫∫"), + (0x222D, "M", "∫∫∫"), + (0x222E, "V"), + (0x222F, "M", "∮∮"), + (0x2230, "M", "∮∮∮"), + (0x2231, "V"), + (0x2329, "M", "〈"), + (0x232A, "M", "〉"), + (0x232B, "V"), + (0x2427, "X"), + (0x2440, "V"), + (0x244B, "X"), + (0x2460, "M", "1"), + (0x2461, "M", "2"), + (0x2462, "M", "3"), + (0x2463, "M", "4"), + (0x2464, "M", "5"), + (0x2465, "M", "6"), + (0x2466, "M", "7"), + (0x2467, "M", "8"), + (0x2468, "M", "9"), + (0x2469, "M", "10"), + (0x246A, "M", "11"), + (0x246B, "M", "12"), + (0x246C, "M", "13"), + (0x246D, "M", "14"), + (0x246E, "M", "15"), + (0x246F, "M", "16"), + (0x2470, "M", "17"), + (0x2471, "M", "18"), + (0x2472, "M", "19"), + (0x2473, "M", "20"), + (0x2474, "3", "(1)"), + (0x2475, "3", "(2)"), + (0x2476, "3", "(3)"), + (0x2477, "3", "(4)"), + (0x2478, "3", "(5)"), + (0x2479, "3", "(6)"), + (0x247A, "3", "(7)"), + (0x247B, "3", "(8)"), + (0x247C, "3", "(9)"), + (0x247D, "3", "(10)"), + (0x247E, "3", "(11)"), + (0x247F, "3", "(12)"), + (0x2480, "3", "(13)"), + (0x2481, "3", "(14)"), + (0x2482, "3", "(15)"), + (0x2483, "3", "(16)"), + (0x2484, "3", "(17)"), + (0x2485, "3", "(18)"), + (0x2486, "3", "(19)"), + (0x2487, "3", "(20)"), + (0x2488, "X"), + (0x249C, "3", "(a)"), + (0x249D, "3", "(b)"), + (0x249E, "3", "(c)"), + (0x249F, "3", "(d)"), + (0x24A0, "3", "(e)"), + (0x24A1, "3", "(f)"), + (0x24A2, "3", "(g)"), + (0x24A3, "3", "(h)"), + (0x24A4, "3", "(i)"), + (0x24A5, "3", "(j)"), + (0x24A6, "3", "(k)"), + (0x24A7, "3", "(l)"), + (0x24A8, "3", "(m)"), + (0x24A9, "3", "(n)"), + (0x24AA, "3", "(o)"), + (0x24AB, "3", "(p)"), + (0x24AC, "3", "(q)"), + (0x24AD, "3", "(r)"), + (0x24AE, "3", "(s)"), + (0x24AF, "3", "(t)"), + (0x24B0, "3", "(u)"), + (0x24B1, "3", "(v)"), + (0x24B2, "3", "(w)"), + (0x24B3, "3", "(x)"), + (0x24B4, "3", "(y)"), + (0x24B5, "3", "(z)"), + (0x24B6, "M", "a"), + (0x24B7, "M", "b"), + (0x24B8, "M", "c"), + (0x24B9, "M", "d"), + (0x24BA, "M", "e"), + (0x24BB, "M", "f"), + (0x24BC, "M", "g"), + (0x24BD, "M", "h"), + (0x24BE, "M", "i"), + (0x24BF, "M", "j"), + (0x24C0, "M", "k"), + ] + + +def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x24C1, "M", "l"), + (0x24C2, "M", "m"), + (0x24C3, "M", "n"), + (0x24C4, "M", "o"), + (0x24C5, "M", "p"), + (0x24C6, "M", "q"), + (0x24C7, "M", "r"), + (0x24C8, "M", "s"), + (0x24C9, "M", "t"), + (0x24CA, "M", "u"), + (0x24CB, "M", "v"), + (0x24CC, "M", "w"), + (0x24CD, "M", "x"), + (0x24CE, "M", "y"), + (0x24CF, "M", "z"), + (0x24D0, "M", "a"), + (0x24D1, "M", "b"), + (0x24D2, "M", "c"), + (0x24D3, "M", "d"), + (0x24D4, "M", "e"), + (0x24D5, "M", "f"), + (0x24D6, "M", "g"), + (0x24D7, "M", "h"), + (0x24D8, "M", "i"), + (0x24D9, "M", "j"), + (0x24DA, "M", "k"), + (0x24DB, "M", "l"), + (0x24DC, "M", "m"), + (0x24DD, "M", "n"), + (0x24DE, "M", "o"), + (0x24DF, "M", "p"), + (0x24E0, "M", "q"), + (0x24E1, "M", "r"), + (0x24E2, "M", "s"), + (0x24E3, "M", "t"), + (0x24E4, "M", "u"), + (0x24E5, "M", "v"), + (0x24E6, "M", "w"), + (0x24E7, "M", "x"), + (0x24E8, "M", "y"), + (0x24E9, "M", "z"), + (0x24EA, "M", "0"), + (0x24EB, "V"), + (0x2A0C, "M", "∫∫∫∫"), + (0x2A0D, "V"), + (0x2A74, "3", "::="), + (0x2A75, "3", "=="), + (0x2A76, "3", "==="), + (0x2A77, "V"), + (0x2ADC, "M", "⫝̸"), + (0x2ADD, "V"), + (0x2B74, "X"), + (0x2B76, "V"), + (0x2B96, "X"), + (0x2B97, "V"), + (0x2C00, "M", "ⰰ"), + (0x2C01, "M", "ⰱ"), + (0x2C02, "M", "ⰲ"), + (0x2C03, "M", "ⰳ"), + (0x2C04, "M", "ⰴ"), + (0x2C05, "M", "ⰵ"), + (0x2C06, "M", "ⰶ"), + (0x2C07, "M", "ⰷ"), + (0x2C08, "M", "ⰸ"), + (0x2C09, "M", "ⰹ"), + (0x2C0A, "M", "ⰺ"), + (0x2C0B, "M", "ⰻ"), + (0x2C0C, "M", "ⰼ"), + (0x2C0D, "M", "ⰽ"), + (0x2C0E, "M", "ⰾ"), + (0x2C0F, "M", "ⰿ"), + (0x2C10, "M", "ⱀ"), + (0x2C11, "M", "ⱁ"), + (0x2C12, "M", "ⱂ"), + (0x2C13, "M", "ⱃ"), + (0x2C14, "M", "ⱄ"), + (0x2C15, "M", "ⱅ"), + (0x2C16, "M", "ⱆ"), + (0x2C17, "M", "ⱇ"), + (0x2C18, "M", "ⱈ"), + (0x2C19, "M", "ⱉ"), + (0x2C1A, "M", "ⱊ"), + (0x2C1B, "M", "ⱋ"), + (0x2C1C, "M", "ⱌ"), + (0x2C1D, "M", "ⱍ"), + (0x2C1E, "M", "ⱎ"), + (0x2C1F, "M", "ⱏ"), + (0x2C20, "M", "ⱐ"), + (0x2C21, "M", "ⱑ"), + (0x2C22, "M", "ⱒ"), + (0x2C23, "M", "ⱓ"), + (0x2C24, "M", "ⱔ"), + (0x2C25, "M", "ⱕ"), + (0x2C26, "M", "ⱖ"), + (0x2C27, "M", "ⱗ"), + (0x2C28, "M", "ⱘ"), + (0x2C29, "M", "ⱙ"), + (0x2C2A, "M", "ⱚ"), + (0x2C2B, "M", "ⱛ"), + (0x2C2C, "M", "ⱜ"), + ] + + +def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2C2D, "M", "ⱝ"), + (0x2C2E, "M", "ⱞ"), + (0x2C2F, "M", "ⱟ"), + (0x2C30, "V"), + (0x2C60, "M", "ⱡ"), + (0x2C61, "V"), + (0x2C62, "M", "ɫ"), + (0x2C63, "M", "ᵽ"), + (0x2C64, "M", "ɽ"), + (0x2C65, "V"), + (0x2C67, "M", "ⱨ"), + (0x2C68, "V"), + (0x2C69, "M", "ⱪ"), + (0x2C6A, "V"), + (0x2C6B, "M", "ⱬ"), + (0x2C6C, "V"), + (0x2C6D, "M", "ɑ"), + (0x2C6E, "M", "ɱ"), + (0x2C6F, "M", "ɐ"), + (0x2C70, "M", "ɒ"), + (0x2C71, "V"), + (0x2C72, "M", "ⱳ"), + (0x2C73, "V"), + (0x2C75, "M", "ⱶ"), + (0x2C76, "V"), + (0x2C7C, "M", "j"), + (0x2C7D, "M", "v"), + (0x2C7E, "M", "ȿ"), + (0x2C7F, "M", "ɀ"), + (0x2C80, "M", "ⲁ"), + (0x2C81, "V"), + (0x2C82, "M", "ⲃ"), + (0x2C83, "V"), + (0x2C84, "M", "ⲅ"), + (0x2C85, "V"), + (0x2C86, "M", "ⲇ"), + (0x2C87, "V"), + (0x2C88, "M", "ⲉ"), + (0x2C89, "V"), + (0x2C8A, "M", "ⲋ"), + (0x2C8B, "V"), + (0x2C8C, "M", "ⲍ"), + (0x2C8D, "V"), + (0x2C8E, "M", "ⲏ"), + (0x2C8F, "V"), + (0x2C90, "M", "ⲑ"), + (0x2C91, "V"), + (0x2C92, "M", "ⲓ"), + (0x2C93, "V"), + (0x2C94, "M", "ⲕ"), + (0x2C95, "V"), + (0x2C96, "M", "ⲗ"), + (0x2C97, "V"), + (0x2C98, "M", "ⲙ"), + (0x2C99, "V"), + (0x2C9A, "M", "ⲛ"), + (0x2C9B, "V"), + (0x2C9C, "M", "ⲝ"), + (0x2C9D, "V"), + (0x2C9E, "M", "ⲟ"), + (0x2C9F, "V"), + (0x2CA0, "M", "ⲡ"), + (0x2CA1, "V"), + (0x2CA2, "M", "ⲣ"), + (0x2CA3, "V"), + (0x2CA4, "M", "ⲥ"), + (0x2CA5, "V"), + (0x2CA6, "M", "ⲧ"), + (0x2CA7, "V"), + (0x2CA8, "M", "ⲩ"), + (0x2CA9, "V"), + (0x2CAA, "M", "ⲫ"), + (0x2CAB, "V"), + (0x2CAC, "M", "ⲭ"), + (0x2CAD, "V"), + (0x2CAE, "M", "ⲯ"), + (0x2CAF, "V"), + (0x2CB0, "M", "ⲱ"), + (0x2CB1, "V"), + (0x2CB2, "M", "ⲳ"), + (0x2CB3, "V"), + (0x2CB4, "M", "ⲵ"), + (0x2CB5, "V"), + (0x2CB6, "M", "ⲷ"), + (0x2CB7, "V"), + (0x2CB8, "M", "ⲹ"), + (0x2CB9, "V"), + (0x2CBA, "M", "ⲻ"), + (0x2CBB, "V"), + (0x2CBC, "M", "ⲽ"), + (0x2CBD, "V"), + (0x2CBE, "M", "ⲿ"), + (0x2CBF, "V"), + (0x2CC0, "M", "ⳁ"), + (0x2CC1, "V"), + (0x2CC2, "M", "ⳃ"), + (0x2CC3, "V"), + (0x2CC4, "M", "ⳅ"), + (0x2CC5, "V"), + (0x2CC6, "M", "ⳇ"), + ] + + +def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2CC7, "V"), + (0x2CC8, "M", "ⳉ"), + (0x2CC9, "V"), + (0x2CCA, "M", "ⳋ"), + (0x2CCB, "V"), + (0x2CCC, "M", "ⳍ"), + (0x2CCD, "V"), + (0x2CCE, "M", "ⳏ"), + (0x2CCF, "V"), + (0x2CD0, "M", "ⳑ"), + (0x2CD1, "V"), + (0x2CD2, "M", "ⳓ"), + (0x2CD3, "V"), + (0x2CD4, "M", "ⳕ"), + (0x2CD5, "V"), + (0x2CD6, "M", "ⳗ"), + (0x2CD7, "V"), + (0x2CD8, "M", "ⳙ"), + (0x2CD9, "V"), + (0x2CDA, "M", "ⳛ"), + (0x2CDB, "V"), + (0x2CDC, "M", "ⳝ"), + (0x2CDD, "V"), + (0x2CDE, "M", "ⳟ"), + (0x2CDF, "V"), + (0x2CE0, "M", "ⳡ"), + (0x2CE1, "V"), + (0x2CE2, "M", "ⳣ"), + (0x2CE3, "V"), + (0x2CEB, "M", "ⳬ"), + (0x2CEC, "V"), + (0x2CED, "M", "ⳮ"), + (0x2CEE, "V"), + (0x2CF2, "M", "ⳳ"), + (0x2CF3, "V"), + (0x2CF4, "X"), + (0x2CF9, "V"), + (0x2D26, "X"), + (0x2D27, "V"), + (0x2D28, "X"), + (0x2D2D, "V"), + (0x2D2E, "X"), + (0x2D30, "V"), + (0x2D68, "X"), + (0x2D6F, "M", "ⵡ"), + (0x2D70, "V"), + (0x2D71, "X"), + (0x2D7F, "V"), + (0x2D97, "X"), + (0x2DA0, "V"), + (0x2DA7, "X"), + (0x2DA8, "V"), + (0x2DAF, "X"), + (0x2DB0, "V"), + (0x2DB7, "X"), + (0x2DB8, "V"), + (0x2DBF, "X"), + (0x2DC0, "V"), + (0x2DC7, "X"), + (0x2DC8, "V"), + (0x2DCF, "X"), + (0x2DD0, "V"), + (0x2DD7, "X"), + (0x2DD8, "V"), + (0x2DDF, "X"), + (0x2DE0, "V"), + (0x2E5E, "X"), + (0x2E80, "V"), + (0x2E9A, "X"), + (0x2E9B, "V"), + (0x2E9F, "M", "母"), + (0x2EA0, "V"), + (0x2EF3, "M", "龟"), + (0x2EF4, "X"), + (0x2F00, "M", "一"), + (0x2F01, "M", "丨"), + (0x2F02, "M", "丶"), + (0x2F03, "M", "丿"), + (0x2F04, "M", "乙"), + (0x2F05, "M", "亅"), + (0x2F06, "M", "二"), + (0x2F07, "M", "亠"), + (0x2F08, "M", "人"), + (0x2F09, "M", "儿"), + (0x2F0A, "M", "入"), + (0x2F0B, "M", "八"), + (0x2F0C, "M", "冂"), + (0x2F0D, "M", "冖"), + (0x2F0E, "M", "冫"), + (0x2F0F, "M", "几"), + (0x2F10, "M", "凵"), + (0x2F11, "M", "刀"), + (0x2F12, "M", "力"), + (0x2F13, "M", "勹"), + (0x2F14, "M", "匕"), + (0x2F15, "M", "匚"), + (0x2F16, "M", "匸"), + (0x2F17, "M", "十"), + (0x2F18, "M", "卜"), + (0x2F19, "M", "卩"), + ] + + +def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F1A, "M", "厂"), + (0x2F1B, "M", "厶"), + (0x2F1C, "M", "又"), + (0x2F1D, "M", "口"), + (0x2F1E, "M", "囗"), + (0x2F1F, "M", "土"), + (0x2F20, "M", "士"), + (0x2F21, "M", "夂"), + (0x2F22, "M", "夊"), + (0x2F23, "M", "夕"), + (0x2F24, "M", "大"), + (0x2F25, "M", "女"), + (0x2F26, "M", "子"), + (0x2F27, "M", "宀"), + (0x2F28, "M", "寸"), + (0x2F29, "M", "小"), + (0x2F2A, "M", "尢"), + (0x2F2B, "M", "尸"), + (0x2F2C, "M", "屮"), + (0x2F2D, "M", "山"), + (0x2F2E, "M", "巛"), + (0x2F2F, "M", "工"), + (0x2F30, "M", "己"), + (0x2F31, "M", "巾"), + (0x2F32, "M", "干"), + (0x2F33, "M", "幺"), + (0x2F34, "M", "广"), + (0x2F35, "M", "廴"), + (0x2F36, "M", "廾"), + (0x2F37, "M", "弋"), + (0x2F38, "M", "弓"), + (0x2F39, "M", "彐"), + (0x2F3A, "M", "彡"), + (0x2F3B, "M", "彳"), + (0x2F3C, "M", "心"), + (0x2F3D, "M", "戈"), + (0x2F3E, "M", "戶"), + (0x2F3F, "M", "手"), + (0x2F40, "M", "支"), + (0x2F41, "M", "攴"), + (0x2F42, "M", "文"), + (0x2F43, "M", "斗"), + (0x2F44, "M", "斤"), + (0x2F45, "M", "方"), + (0x2F46, "M", "无"), + (0x2F47, "M", "日"), + (0x2F48, "M", "曰"), + (0x2F49, "M", "月"), + (0x2F4A, "M", "木"), + (0x2F4B, "M", "欠"), + (0x2F4C, "M", "止"), + (0x2F4D, "M", "歹"), + (0x2F4E, "M", "殳"), + (0x2F4F, "M", "毋"), + (0x2F50, "M", "比"), + (0x2F51, "M", "毛"), + (0x2F52, "M", "氏"), + (0x2F53, "M", "气"), + (0x2F54, "M", "水"), + (0x2F55, "M", "火"), + (0x2F56, "M", "爪"), + (0x2F57, "M", "父"), + (0x2F58, "M", "爻"), + (0x2F59, "M", "爿"), + (0x2F5A, "M", "片"), + (0x2F5B, "M", "牙"), + (0x2F5C, "M", "牛"), + (0x2F5D, "M", "犬"), + (0x2F5E, "M", "玄"), + (0x2F5F, "M", "玉"), + (0x2F60, "M", "瓜"), + (0x2F61, "M", "瓦"), + (0x2F62, "M", "甘"), + (0x2F63, "M", "生"), + (0x2F64, "M", "用"), + (0x2F65, "M", "田"), + (0x2F66, "M", "疋"), + (0x2F67, "M", "疒"), + (0x2F68, "M", "癶"), + (0x2F69, "M", "白"), + (0x2F6A, "M", "皮"), + (0x2F6B, "M", "皿"), + (0x2F6C, "M", "目"), + (0x2F6D, "M", "矛"), + (0x2F6E, "M", "矢"), + (0x2F6F, "M", "石"), + (0x2F70, "M", "示"), + (0x2F71, "M", "禸"), + (0x2F72, "M", "禾"), + (0x2F73, "M", "穴"), + (0x2F74, "M", "立"), + (0x2F75, "M", "竹"), + (0x2F76, "M", "米"), + (0x2F77, "M", "糸"), + (0x2F78, "M", "缶"), + (0x2F79, "M", "网"), + (0x2F7A, "M", "羊"), + (0x2F7B, "M", "羽"), + (0x2F7C, "M", "老"), + (0x2F7D, "M", "而"), + ] + + +def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F7E, "M", "耒"), + (0x2F7F, "M", "耳"), + (0x2F80, "M", "聿"), + (0x2F81, "M", "肉"), + (0x2F82, "M", "臣"), + (0x2F83, "M", "自"), + (0x2F84, "M", "至"), + (0x2F85, "M", "臼"), + (0x2F86, "M", "舌"), + (0x2F87, "M", "舛"), + (0x2F88, "M", "舟"), + (0x2F89, "M", "艮"), + (0x2F8A, "M", "色"), + (0x2F8B, "M", "艸"), + (0x2F8C, "M", "虍"), + (0x2F8D, "M", "虫"), + (0x2F8E, "M", "血"), + (0x2F8F, "M", "行"), + (0x2F90, "M", "衣"), + (0x2F91, "M", "襾"), + (0x2F92, "M", "見"), + (0x2F93, "M", "角"), + (0x2F94, "M", "言"), + (0x2F95, "M", "谷"), + (0x2F96, "M", "豆"), + (0x2F97, "M", "豕"), + (0x2F98, "M", "豸"), + (0x2F99, "M", "貝"), + (0x2F9A, "M", "赤"), + (0x2F9B, "M", "走"), + (0x2F9C, "M", "足"), + (0x2F9D, "M", "身"), + (0x2F9E, "M", "車"), + (0x2F9F, "M", "辛"), + (0x2FA0, "M", "辰"), + (0x2FA1, "M", "辵"), + (0x2FA2, "M", "邑"), + (0x2FA3, "M", "酉"), + (0x2FA4, "M", "釆"), + (0x2FA5, "M", "里"), + (0x2FA6, "M", "金"), + (0x2FA7, "M", "長"), + (0x2FA8, "M", "門"), + (0x2FA9, "M", "阜"), + (0x2FAA, "M", "隶"), + (0x2FAB, "M", "隹"), + (0x2FAC, "M", "雨"), + (0x2FAD, "M", "靑"), + (0x2FAE, "M", "非"), + (0x2FAF, "M", "面"), + (0x2FB0, "M", "革"), + (0x2FB1, "M", "韋"), + (0x2FB2, "M", "韭"), + (0x2FB3, "M", "音"), + (0x2FB4, "M", "頁"), + (0x2FB5, "M", "風"), + (0x2FB6, "M", "飛"), + (0x2FB7, "M", "食"), + (0x2FB8, "M", "首"), + (0x2FB9, "M", "香"), + (0x2FBA, "M", "馬"), + (0x2FBB, "M", "骨"), + (0x2FBC, "M", "高"), + (0x2FBD, "M", "髟"), + (0x2FBE, "M", "鬥"), + (0x2FBF, "M", "鬯"), + (0x2FC0, "M", "鬲"), + (0x2FC1, "M", "鬼"), + (0x2FC2, "M", "魚"), + (0x2FC3, "M", "鳥"), + (0x2FC4, "M", "鹵"), + (0x2FC5, "M", "鹿"), + (0x2FC6, "M", "麥"), + (0x2FC7, "M", "麻"), + (0x2FC8, "M", "黃"), + (0x2FC9, "M", "黍"), + (0x2FCA, "M", "黑"), + (0x2FCB, "M", "黹"), + (0x2FCC, "M", "黽"), + (0x2FCD, "M", "鼎"), + (0x2FCE, "M", "鼓"), + (0x2FCF, "M", "鼠"), + (0x2FD0, "M", "鼻"), + (0x2FD1, "M", "齊"), + (0x2FD2, "M", "齒"), + (0x2FD3, "M", "龍"), + (0x2FD4, "M", "龜"), + (0x2FD5, "M", "龠"), + (0x2FD6, "X"), + (0x3000, "3", " "), + (0x3001, "V"), + (0x3002, "M", "."), + (0x3003, "V"), + (0x3036, "M", "〒"), + (0x3037, "V"), + (0x3038, "M", "十"), + (0x3039, "M", "卄"), + (0x303A, "M", "卅"), + (0x303B, "V"), + (0x3040, "X"), + ] + + +def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3041, "V"), + (0x3097, "X"), + (0x3099, "V"), + (0x309B, "3", " ゙"), + (0x309C, "3", " ゚"), + (0x309D, "V"), + (0x309F, "M", "より"), + (0x30A0, "V"), + (0x30FF, "M", "コト"), + (0x3100, "X"), + (0x3105, "V"), + (0x3130, "X"), + (0x3131, "M", "ᄀ"), + (0x3132, "M", "ᄁ"), + (0x3133, "M", "ᆪ"), + (0x3134, "M", "ᄂ"), + (0x3135, "M", "ᆬ"), + (0x3136, "M", "ᆭ"), + (0x3137, "M", "ᄃ"), + (0x3138, "M", "ᄄ"), + (0x3139, "M", "ᄅ"), + (0x313A, "M", "ᆰ"), + (0x313B, "M", "ᆱ"), + (0x313C, "M", "ᆲ"), + (0x313D, "M", "ᆳ"), + (0x313E, "M", "ᆴ"), + (0x313F, "M", "ᆵ"), + (0x3140, "M", "ᄚ"), + (0x3141, "M", "ᄆ"), + (0x3142, "M", "ᄇ"), + (0x3143, "M", "ᄈ"), + (0x3144, "M", "ᄡ"), + (0x3145, "M", "ᄉ"), + (0x3146, "M", "ᄊ"), + (0x3147, "M", "ᄋ"), + (0x3148, "M", "ᄌ"), + (0x3149, "M", "ᄍ"), + (0x314A, "M", "ᄎ"), + (0x314B, "M", "ᄏ"), + (0x314C, "M", "ᄐ"), + (0x314D, "M", "ᄑ"), + (0x314E, "M", "ᄒ"), + (0x314F, "M", "ᅡ"), + (0x3150, "M", "ᅢ"), + (0x3151, "M", "ᅣ"), + (0x3152, "M", "ᅤ"), + (0x3153, "M", "ᅥ"), + (0x3154, "M", "ᅦ"), + (0x3155, "M", "ᅧ"), + (0x3156, "M", "ᅨ"), + (0x3157, "M", "ᅩ"), + (0x3158, "M", "ᅪ"), + (0x3159, "M", "ᅫ"), + (0x315A, "M", "ᅬ"), + (0x315B, "M", "ᅭ"), + (0x315C, "M", "ᅮ"), + (0x315D, "M", "ᅯ"), + (0x315E, "M", "ᅰ"), + (0x315F, "M", "ᅱ"), + (0x3160, "M", "ᅲ"), + (0x3161, "M", "ᅳ"), + (0x3162, "M", "ᅴ"), + (0x3163, "M", "ᅵ"), + (0x3164, "X"), + (0x3165, "M", "ᄔ"), + (0x3166, "M", "ᄕ"), + (0x3167, "M", "ᇇ"), + (0x3168, "M", "ᇈ"), + (0x3169, "M", "ᇌ"), + (0x316A, "M", "ᇎ"), + (0x316B, "M", "ᇓ"), + (0x316C, "M", "ᇗ"), + (0x316D, "M", "ᇙ"), + (0x316E, "M", "ᄜ"), + (0x316F, "M", "ᇝ"), + (0x3170, "M", "ᇟ"), + (0x3171, "M", "ᄝ"), + (0x3172, "M", "ᄞ"), + (0x3173, "M", "ᄠ"), + (0x3174, "M", "ᄢ"), + (0x3175, "M", "ᄣ"), + (0x3176, "M", "ᄧ"), + (0x3177, "M", "ᄩ"), + (0x3178, "M", "ᄫ"), + (0x3179, "M", "ᄬ"), + (0x317A, "M", "ᄭ"), + (0x317B, "M", "ᄮ"), + (0x317C, "M", "ᄯ"), + (0x317D, "M", "ᄲ"), + (0x317E, "M", "ᄶ"), + (0x317F, "M", "ᅀ"), + (0x3180, "M", "ᅇ"), + (0x3181, "M", "ᅌ"), + (0x3182, "M", "ᇱ"), + (0x3183, "M", "ᇲ"), + (0x3184, "M", "ᅗ"), + (0x3185, "M", "ᅘ"), + (0x3186, "M", "ᅙ"), + (0x3187, "M", "ᆄ"), + (0x3188, "M", "ᆅ"), + ] + + +def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3189, "M", "ᆈ"), + (0x318A, "M", "ᆑ"), + (0x318B, "M", "ᆒ"), + (0x318C, "M", "ᆔ"), + (0x318D, "M", "ᆞ"), + (0x318E, "M", "ᆡ"), + (0x318F, "X"), + (0x3190, "V"), + (0x3192, "M", "一"), + (0x3193, "M", "二"), + (0x3194, "M", "三"), + (0x3195, "M", "四"), + (0x3196, "M", "上"), + (0x3197, "M", "中"), + (0x3198, "M", "下"), + (0x3199, "M", "甲"), + (0x319A, "M", "乙"), + (0x319B, "M", "丙"), + (0x319C, "M", "丁"), + (0x319D, "M", "天"), + (0x319E, "M", "地"), + (0x319F, "M", "人"), + (0x31A0, "V"), + (0x31E4, "X"), + (0x31F0, "V"), + (0x3200, "3", "(ᄀ)"), + (0x3201, "3", "(ᄂ)"), + (0x3202, "3", "(ᄃ)"), + (0x3203, "3", "(ᄅ)"), + (0x3204, "3", "(ᄆ)"), + (0x3205, "3", "(ᄇ)"), + (0x3206, "3", "(ᄉ)"), + (0x3207, "3", "(ᄋ)"), + (0x3208, "3", "(ᄌ)"), + (0x3209, "3", "(ᄎ)"), + (0x320A, "3", "(ᄏ)"), + (0x320B, "3", "(ᄐ)"), + (0x320C, "3", "(ᄑ)"), + (0x320D, "3", "(ᄒ)"), + (0x320E, "3", "(가)"), + (0x320F, "3", "(나)"), + (0x3210, "3", "(다)"), + (0x3211, "3", "(라)"), + (0x3212, "3", "(마)"), + (0x3213, "3", "(바)"), + (0x3214, "3", "(사)"), + (0x3215, "3", "(아)"), + (0x3216, "3", "(자)"), + (0x3217, "3", "(차)"), + (0x3218, "3", "(카)"), + (0x3219, "3", "(타)"), + (0x321A, "3", "(파)"), + (0x321B, "3", "(하)"), + (0x321C, "3", "(주)"), + (0x321D, "3", "(오전)"), + (0x321E, "3", "(오후)"), + (0x321F, "X"), + (0x3220, "3", "(一)"), + (0x3221, "3", "(二)"), + (0x3222, "3", "(三)"), + (0x3223, "3", "(四)"), + (0x3224, "3", "(五)"), + (0x3225, "3", "(六)"), + (0x3226, "3", "(七)"), + (0x3227, "3", "(八)"), + (0x3228, "3", "(九)"), + (0x3229, "3", "(十)"), + (0x322A, "3", "(月)"), + (0x322B, "3", "(火)"), + (0x322C, "3", "(水)"), + (0x322D, "3", "(木)"), + (0x322E, "3", "(金)"), + (0x322F, "3", "(土)"), + (0x3230, "3", "(日)"), + (0x3231, "3", "(株)"), + (0x3232, "3", "(有)"), + (0x3233, "3", "(社)"), + (0x3234, "3", "(名)"), + (0x3235, "3", "(特)"), + (0x3236, "3", "(財)"), + (0x3237, "3", "(祝)"), + (0x3238, "3", "(労)"), + (0x3239, "3", "(代)"), + (0x323A, "3", "(呼)"), + (0x323B, "3", "(学)"), + (0x323C, "3", "(監)"), + (0x323D, "3", "(企)"), + (0x323E, "3", "(資)"), + (0x323F, "3", "(協)"), + (0x3240, "3", "(祭)"), + (0x3241, "3", "(休)"), + (0x3242, "3", "(自)"), + (0x3243, "3", "(至)"), + (0x3244, "M", "問"), + (0x3245, "M", "幼"), + (0x3246, "M", "文"), + (0x3247, "M", "箏"), + (0x3248, "V"), + (0x3250, "M", "pte"), + (0x3251, "M", "21"), + ] + + +def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x3252, "M", "22"), + (0x3253, "M", "23"), + (0x3254, "M", "24"), + (0x3255, "M", "25"), + (0x3256, "M", "26"), + (0x3257, "M", "27"), + (0x3258, "M", "28"), + (0x3259, "M", "29"), + (0x325A, "M", "30"), + (0x325B, "M", "31"), + (0x325C, "M", "32"), + (0x325D, "M", "33"), + (0x325E, "M", "34"), + (0x325F, "M", "35"), + (0x3260, "M", "ᄀ"), + (0x3261, "M", "ᄂ"), + (0x3262, "M", "ᄃ"), + (0x3263, "M", "ᄅ"), + (0x3264, "M", "ᄆ"), + (0x3265, "M", "ᄇ"), + (0x3266, "M", "ᄉ"), + (0x3267, "M", "ᄋ"), + (0x3268, "M", "ᄌ"), + (0x3269, "M", "ᄎ"), + (0x326A, "M", "ᄏ"), + (0x326B, "M", "ᄐ"), + (0x326C, "M", "ᄑ"), + (0x326D, "M", "ᄒ"), + (0x326E, "M", "가"), + (0x326F, "M", "나"), + (0x3270, "M", "다"), + (0x3271, "M", "라"), + (0x3272, "M", "마"), + (0x3273, "M", "바"), + (0x3274, "M", "사"), + (0x3275, "M", "아"), + (0x3276, "M", "자"), + (0x3277, "M", "차"), + (0x3278, "M", "카"), + (0x3279, "M", "타"), + (0x327A, "M", "파"), + (0x327B, "M", "하"), + (0x327C, "M", "참고"), + (0x327D, "M", "주의"), + (0x327E, "M", "우"), + (0x327F, "V"), + (0x3280, "M", "一"), + (0x3281, "M", "二"), + (0x3282, "M", "三"), + (0x3283, "M", "四"), + (0x3284, "M", "五"), + (0x3285, "M", "六"), + (0x3286, "M", "七"), + (0x3287, "M", "八"), + (0x3288, "M", "九"), + (0x3289, "M", "十"), + (0x328A, "M", "月"), + (0x328B, "M", "火"), + (0x328C, "M", "水"), + (0x328D, "M", "木"), + (0x328E, "M", "金"), + (0x328F, "M", "土"), + (0x3290, "M", "日"), + (0x3291, "M", "株"), + (0x3292, "M", "有"), + (0x3293, "M", "社"), + (0x3294, "M", "名"), + (0x3295, "M", "特"), + (0x3296, "M", "財"), + (0x3297, "M", "祝"), + (0x3298, "M", "労"), + (0x3299, "M", "秘"), + (0x329A, "M", "男"), + (0x329B, "M", "女"), + (0x329C, "M", "適"), + (0x329D, "M", "優"), + (0x329E, "M", "印"), + (0x329F, "M", "注"), + (0x32A0, "M", "項"), + (0x32A1, "M", "休"), + (0x32A2, "M", "写"), + (0x32A3, "M", "正"), + (0x32A4, "M", "上"), + (0x32A5, "M", "中"), + (0x32A6, "M", "下"), + (0x32A7, "M", "左"), + (0x32A8, "M", "右"), + (0x32A9, "M", "医"), + (0x32AA, "M", "宗"), + (0x32AB, "M", "学"), + (0x32AC, "M", "監"), + (0x32AD, "M", "企"), + (0x32AE, "M", "資"), + (0x32AF, "M", "協"), + (0x32B0, "M", "夜"), + (0x32B1, "M", "36"), + (0x32B2, "M", "37"), + (0x32B3, "M", "38"), + (0x32B4, "M", "39"), + (0x32B5, "M", "40"), + ] + + +def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x32B6, "M", "41"), + (0x32B7, "M", "42"), + (0x32B8, "M", "43"), + (0x32B9, "M", "44"), + (0x32BA, "M", "45"), + (0x32BB, "M", "46"), + (0x32BC, "M", "47"), + (0x32BD, "M", "48"), + (0x32BE, "M", "49"), + (0x32BF, "M", "50"), + (0x32C0, "M", "1月"), + (0x32C1, "M", "2月"), + (0x32C2, "M", "3月"), + (0x32C3, "M", "4月"), + (0x32C4, "M", "5月"), + (0x32C5, "M", "6月"), + (0x32C6, "M", "7月"), + (0x32C7, "M", "8月"), + (0x32C8, "M", "9月"), + (0x32C9, "M", "10月"), + (0x32CA, "M", "11月"), + (0x32CB, "M", "12月"), + (0x32CC, "M", "hg"), + (0x32CD, "M", "erg"), + (0x32CE, "M", "ev"), + (0x32CF, "M", "ltd"), + (0x32D0, "M", "ア"), + (0x32D1, "M", "イ"), + (0x32D2, "M", "ウ"), + (0x32D3, "M", "エ"), + (0x32D4, "M", "オ"), + (0x32D5, "M", "カ"), + (0x32D6, "M", "キ"), + (0x32D7, "M", "ク"), + (0x32D8, "M", "ケ"), + (0x32D9, "M", "コ"), + (0x32DA, "M", "サ"), + (0x32DB, "M", "シ"), + (0x32DC, "M", "ス"), + (0x32DD, "M", "セ"), + (0x32DE, "M", "ソ"), + (0x32DF, "M", "タ"), + (0x32E0, "M", "チ"), + (0x32E1, "M", "ツ"), + (0x32E2, "M", "テ"), + (0x32E3, "M", "ト"), + (0x32E4, "M", "ナ"), + (0x32E5, "M", "ニ"), + (0x32E6, "M", "ヌ"), + (0x32E7, "M", "ネ"), + (0x32E8, "M", "ノ"), + (0x32E9, "M", "ハ"), + (0x32EA, "M", "ヒ"), + (0x32EB, "M", "フ"), + (0x32EC, "M", "ヘ"), + (0x32ED, "M", "ホ"), + (0x32EE, "M", "マ"), + (0x32EF, "M", "ミ"), + (0x32F0, "M", "ム"), + (0x32F1, "M", "メ"), + (0x32F2, "M", "モ"), + (0x32F3, "M", "ヤ"), + (0x32F4, "M", "ユ"), + (0x32F5, "M", "ヨ"), + (0x32F6, "M", "ラ"), + (0x32F7, "M", "リ"), + (0x32F8, "M", "ル"), + (0x32F9, "M", "レ"), + (0x32FA, "M", "ロ"), + (0x32FB, "M", "ワ"), + (0x32FC, "M", "ヰ"), + (0x32FD, "M", "ヱ"), + (0x32FE, "M", "ヲ"), + (0x32FF, "M", "令和"), + (0x3300, "M", "アパート"), + (0x3301, "M", "アルファ"), + (0x3302, "M", "アンペア"), + (0x3303, "M", "アール"), + (0x3304, "M", "イニング"), + (0x3305, "M", "インチ"), + (0x3306, "M", "ウォン"), + (0x3307, "M", "エスクード"), + (0x3308, "M", "エーカー"), + (0x3309, "M", "オンス"), + (0x330A, "M", "オーム"), + (0x330B, "M", "カイリ"), + (0x330C, "M", "カラット"), + (0x330D, "M", "カロリー"), + (0x330E, "M", "ガロン"), + (0x330F, "M", "ガンマ"), + (0x3310, "M", "ギガ"), + (0x3311, "M", "ギニー"), + (0x3312, "M", "キュリー"), + (0x3313, "M", "ギルダー"), + (0x3314, "M", "キロ"), + (0x3315, "M", "キログラム"), + (0x3316, "M", "キロメートル"), + (0x3317, "M", "キロワット"), + (0x3318, "M", "グラム"), + (0x3319, "M", "グラムトン"), + ] + + +def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x331A, "M", "クルゼイロ"), + (0x331B, "M", "クローネ"), + (0x331C, "M", "ケース"), + (0x331D, "M", "コルナ"), + (0x331E, "M", "コーポ"), + (0x331F, "M", "サイクル"), + (0x3320, "M", "サンチーム"), + (0x3321, "M", "シリング"), + (0x3322, "M", "センチ"), + (0x3323, "M", "セント"), + (0x3324, "M", "ダース"), + (0x3325, "M", "デシ"), + (0x3326, "M", "ドル"), + (0x3327, "M", "トン"), + (0x3328, "M", "ナノ"), + (0x3329, "M", "ノット"), + (0x332A, "M", "ハイツ"), + (0x332B, "M", "パーセント"), + (0x332C, "M", "パーツ"), + (0x332D, "M", "バーレル"), + (0x332E, "M", "ピアストル"), + (0x332F, "M", "ピクル"), + (0x3330, "M", "ピコ"), + (0x3331, "M", "ビル"), + (0x3332, "M", "ファラッド"), + (0x3333, "M", "フィート"), + (0x3334, "M", "ブッシェル"), + (0x3335, "M", "フラン"), + (0x3336, "M", "ヘクタール"), + (0x3337, "M", "ペソ"), + (0x3338, "M", "ペニヒ"), + (0x3339, "M", "ヘルツ"), + (0x333A, "M", "ペンス"), + (0x333B, "M", "ページ"), + (0x333C, "M", "ベータ"), + (0x333D, "M", "ポイント"), + (0x333E, "M", "ボルト"), + (0x333F, "M", "ホン"), + (0x3340, "M", "ポンド"), + (0x3341, "M", "ホール"), + (0x3342, "M", "ホーン"), + (0x3343, "M", "マイクロ"), + (0x3344, "M", "マイル"), + (0x3345, "M", "マッハ"), + (0x3346, "M", "マルク"), + (0x3347, "M", "マンション"), + (0x3348, "M", "ミクロン"), + (0x3349, "M", "ミリ"), + (0x334A, "M", "ミリバール"), + (0x334B, "M", "メガ"), + (0x334C, "M", "メガトン"), + (0x334D, "M", "メートル"), + (0x334E, "M", "ヤード"), + (0x334F, "M", "ヤール"), + (0x3350, "M", "ユアン"), + (0x3351, "M", "リットル"), + (0x3352, "M", "リラ"), + (0x3353, "M", "ルピー"), + (0x3354, "M", "ルーブル"), + (0x3355, "M", "レム"), + (0x3356, "M", "レントゲン"), + (0x3357, "M", "ワット"), + (0x3358, "M", "0点"), + (0x3359, "M", "1点"), + (0x335A, "M", "2点"), + (0x335B, "M", "3点"), + (0x335C, "M", "4点"), + (0x335D, "M", "5点"), + (0x335E, "M", "6点"), + (0x335F, "M", "7点"), + (0x3360, "M", "8点"), + (0x3361, "M", "9点"), + (0x3362, "M", "10点"), + (0x3363, "M", "11点"), + (0x3364, "M", "12点"), + (0x3365, "M", "13点"), + (0x3366, "M", "14点"), + (0x3367, "M", "15点"), + (0x3368, "M", "16点"), + (0x3369, "M", "17点"), + (0x336A, "M", "18点"), + (0x336B, "M", "19点"), + (0x336C, "M", "20点"), + (0x336D, "M", "21点"), + (0x336E, "M", "22点"), + (0x336F, "M", "23点"), + (0x3370, "M", "24点"), + (0x3371, "M", "hpa"), + (0x3372, "M", "da"), + (0x3373, "M", "au"), + (0x3374, "M", "bar"), + (0x3375, "M", "ov"), + (0x3376, "M", "pc"), + (0x3377, "M", "dm"), + (0x3378, "M", "dm2"), + (0x3379, "M", "dm3"), + (0x337A, "M", "iu"), + (0x337B, "M", "平成"), + (0x337C, "M", "昭和"), + (0x337D, "M", "大正"), + ] + + +def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x337E, "M", "明治"), + (0x337F, "M", "株式会社"), + (0x3380, "M", "pa"), + (0x3381, "M", "na"), + (0x3382, "M", "μa"), + (0x3383, "M", "ma"), + (0x3384, "M", "ka"), + (0x3385, "M", "kb"), + (0x3386, "M", "mb"), + (0x3387, "M", "gb"), + (0x3388, "M", "cal"), + (0x3389, "M", "kcal"), + (0x338A, "M", "pf"), + (0x338B, "M", "nf"), + (0x338C, "M", "μf"), + (0x338D, "M", "μg"), + (0x338E, "M", "mg"), + (0x338F, "M", "kg"), + (0x3390, "M", "hz"), + (0x3391, "M", "khz"), + (0x3392, "M", "mhz"), + (0x3393, "M", "ghz"), + (0x3394, "M", "thz"), + (0x3395, "M", "μl"), + (0x3396, "M", "ml"), + (0x3397, "M", "dl"), + (0x3398, "M", "kl"), + (0x3399, "M", "fm"), + (0x339A, "M", "nm"), + (0x339B, "M", "μm"), + (0x339C, "M", "mm"), + (0x339D, "M", "cm"), + (0x339E, "M", "km"), + (0x339F, "M", "mm2"), + (0x33A0, "M", "cm2"), + (0x33A1, "M", "m2"), + (0x33A2, "M", "km2"), + (0x33A3, "M", "mm3"), + (0x33A4, "M", "cm3"), + (0x33A5, "M", "m3"), + (0x33A6, "M", "km3"), + (0x33A7, "M", "m∕s"), + (0x33A8, "M", "m∕s2"), + (0x33A9, "M", "pa"), + (0x33AA, "M", "kpa"), + (0x33AB, "M", "mpa"), + (0x33AC, "M", "gpa"), + (0x33AD, "M", "rad"), + (0x33AE, "M", "rad∕s"), + (0x33AF, "M", "rad∕s2"), + (0x33B0, "M", "ps"), + (0x33B1, "M", "ns"), + (0x33B2, "M", "μs"), + (0x33B3, "M", "ms"), + (0x33B4, "M", "pv"), + (0x33B5, "M", "nv"), + (0x33B6, "M", "μv"), + (0x33B7, "M", "mv"), + (0x33B8, "M", "kv"), + (0x33B9, "M", "mv"), + (0x33BA, "M", "pw"), + (0x33BB, "M", "nw"), + (0x33BC, "M", "μw"), + (0x33BD, "M", "mw"), + (0x33BE, "M", "kw"), + (0x33BF, "M", "mw"), + (0x33C0, "M", "kω"), + (0x33C1, "M", "mω"), + (0x33C2, "X"), + (0x33C3, "M", "bq"), + (0x33C4, "M", "cc"), + (0x33C5, "M", "cd"), + (0x33C6, "M", "c∕kg"), + (0x33C7, "X"), + (0x33C8, "M", "db"), + (0x33C9, "M", "gy"), + (0x33CA, "M", "ha"), + (0x33CB, "M", "hp"), + (0x33CC, "M", "in"), + (0x33CD, "M", "kk"), + (0x33CE, "M", "km"), + (0x33CF, "M", "kt"), + (0x33D0, "M", "lm"), + (0x33D1, "M", "ln"), + (0x33D2, "M", "log"), + (0x33D3, "M", "lx"), + (0x33D4, "M", "mb"), + (0x33D5, "M", "mil"), + (0x33D6, "M", "mol"), + (0x33D7, "M", "ph"), + (0x33D8, "X"), + (0x33D9, "M", "ppm"), + (0x33DA, "M", "pr"), + (0x33DB, "M", "sr"), + (0x33DC, "M", "sv"), + (0x33DD, "M", "wb"), + (0x33DE, "M", "v∕m"), + (0x33DF, "M", "a∕m"), + (0x33E0, "M", "1日"), + (0x33E1, "M", "2日"), + ] + + +def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x33E2, "M", "3日"), + (0x33E3, "M", "4日"), + (0x33E4, "M", "5日"), + (0x33E5, "M", "6日"), + (0x33E6, "M", "7日"), + (0x33E7, "M", "8日"), + (0x33E8, "M", "9日"), + (0x33E9, "M", "10日"), + (0x33EA, "M", "11日"), + (0x33EB, "M", "12日"), + (0x33EC, "M", "13日"), + (0x33ED, "M", "14日"), + (0x33EE, "M", "15日"), + (0x33EF, "M", "16日"), + (0x33F0, "M", "17日"), + (0x33F1, "M", "18日"), + (0x33F2, "M", "19日"), + (0x33F3, "M", "20日"), + (0x33F4, "M", "21日"), + (0x33F5, "M", "22日"), + (0x33F6, "M", "23日"), + (0x33F7, "M", "24日"), + (0x33F8, "M", "25日"), + (0x33F9, "M", "26日"), + (0x33FA, "M", "27日"), + (0x33FB, "M", "28日"), + (0x33FC, "M", "29日"), + (0x33FD, "M", "30日"), + (0x33FE, "M", "31日"), + (0x33FF, "M", "gal"), + (0x3400, "V"), + (0xA48D, "X"), + (0xA490, "V"), + (0xA4C7, "X"), + (0xA4D0, "V"), + (0xA62C, "X"), + (0xA640, "M", "ꙁ"), + (0xA641, "V"), + (0xA642, "M", "ꙃ"), + (0xA643, "V"), + (0xA644, "M", "ꙅ"), + (0xA645, "V"), + (0xA646, "M", "ꙇ"), + (0xA647, "V"), + (0xA648, "M", "ꙉ"), + (0xA649, "V"), + (0xA64A, "M", "ꙋ"), + (0xA64B, "V"), + (0xA64C, "M", "ꙍ"), + (0xA64D, "V"), + (0xA64E, "M", "ꙏ"), + (0xA64F, "V"), + (0xA650, "M", "ꙑ"), + (0xA651, "V"), + (0xA652, "M", "ꙓ"), + (0xA653, "V"), + (0xA654, "M", "ꙕ"), + (0xA655, "V"), + (0xA656, "M", "ꙗ"), + (0xA657, "V"), + (0xA658, "M", "ꙙ"), + (0xA659, "V"), + (0xA65A, "M", "ꙛ"), + (0xA65B, "V"), + (0xA65C, "M", "ꙝ"), + (0xA65D, "V"), + (0xA65E, "M", "ꙟ"), + (0xA65F, "V"), + (0xA660, "M", "ꙡ"), + (0xA661, "V"), + (0xA662, "M", "ꙣ"), + (0xA663, "V"), + (0xA664, "M", "ꙥ"), + (0xA665, "V"), + (0xA666, "M", "ꙧ"), + (0xA667, "V"), + (0xA668, "M", "ꙩ"), + (0xA669, "V"), + (0xA66A, "M", "ꙫ"), + (0xA66B, "V"), + (0xA66C, "M", "ꙭ"), + (0xA66D, "V"), + (0xA680, "M", "ꚁ"), + (0xA681, "V"), + (0xA682, "M", "ꚃ"), + (0xA683, "V"), + (0xA684, "M", "ꚅ"), + (0xA685, "V"), + (0xA686, "M", "ꚇ"), + (0xA687, "V"), + (0xA688, "M", "ꚉ"), + (0xA689, "V"), + (0xA68A, "M", "ꚋ"), + (0xA68B, "V"), + (0xA68C, "M", "ꚍ"), + (0xA68D, "V"), + (0xA68E, "M", "ꚏ"), + (0xA68F, "V"), + (0xA690, "M", "ꚑ"), + (0xA691, "V"), + ] + + +def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA692, "M", "ꚓ"), + (0xA693, "V"), + (0xA694, "M", "ꚕ"), + (0xA695, "V"), + (0xA696, "M", "ꚗ"), + (0xA697, "V"), + (0xA698, "M", "ꚙ"), + (0xA699, "V"), + (0xA69A, "M", "ꚛ"), + (0xA69B, "V"), + (0xA69C, "M", "ъ"), + (0xA69D, "M", "ь"), + (0xA69E, "V"), + (0xA6F8, "X"), + (0xA700, "V"), + (0xA722, "M", "ꜣ"), + (0xA723, "V"), + (0xA724, "M", "ꜥ"), + (0xA725, "V"), + (0xA726, "M", "ꜧ"), + (0xA727, "V"), + (0xA728, "M", "ꜩ"), + (0xA729, "V"), + (0xA72A, "M", "ꜫ"), + (0xA72B, "V"), + (0xA72C, "M", "ꜭ"), + (0xA72D, "V"), + (0xA72E, "M", "ꜯ"), + (0xA72F, "V"), + (0xA732, "M", "ꜳ"), + (0xA733, "V"), + (0xA734, "M", "ꜵ"), + (0xA735, "V"), + (0xA736, "M", "ꜷ"), + (0xA737, "V"), + (0xA738, "M", "ꜹ"), + (0xA739, "V"), + (0xA73A, "M", "ꜻ"), + (0xA73B, "V"), + (0xA73C, "M", "ꜽ"), + (0xA73D, "V"), + (0xA73E, "M", "ꜿ"), + (0xA73F, "V"), + (0xA740, "M", "ꝁ"), + (0xA741, "V"), + (0xA742, "M", "ꝃ"), + (0xA743, "V"), + (0xA744, "M", "ꝅ"), + (0xA745, "V"), + (0xA746, "M", "ꝇ"), + (0xA747, "V"), + (0xA748, "M", "ꝉ"), + (0xA749, "V"), + (0xA74A, "M", "ꝋ"), + (0xA74B, "V"), + (0xA74C, "M", "ꝍ"), + (0xA74D, "V"), + (0xA74E, "M", "ꝏ"), + (0xA74F, "V"), + (0xA750, "M", "ꝑ"), + (0xA751, "V"), + (0xA752, "M", "ꝓ"), + (0xA753, "V"), + (0xA754, "M", "ꝕ"), + (0xA755, "V"), + (0xA756, "M", "ꝗ"), + (0xA757, "V"), + (0xA758, "M", "ꝙ"), + (0xA759, "V"), + (0xA75A, "M", "ꝛ"), + (0xA75B, "V"), + (0xA75C, "M", "ꝝ"), + (0xA75D, "V"), + (0xA75E, "M", "ꝟ"), + (0xA75F, "V"), + (0xA760, "M", "ꝡ"), + (0xA761, "V"), + (0xA762, "M", "ꝣ"), + (0xA763, "V"), + (0xA764, "M", "ꝥ"), + (0xA765, "V"), + (0xA766, "M", "ꝧ"), + (0xA767, "V"), + (0xA768, "M", "ꝩ"), + (0xA769, "V"), + (0xA76A, "M", "ꝫ"), + (0xA76B, "V"), + (0xA76C, "M", "ꝭ"), + (0xA76D, "V"), + (0xA76E, "M", "ꝯ"), + (0xA76F, "V"), + (0xA770, "M", "ꝯ"), + (0xA771, "V"), + (0xA779, "M", "ꝺ"), + (0xA77A, "V"), + (0xA77B, "M", "ꝼ"), + (0xA77C, "V"), + (0xA77D, "M", "ᵹ"), + (0xA77E, "M", "ꝿ"), + (0xA77F, "V"), + ] + + +def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA780, "M", "ꞁ"), + (0xA781, "V"), + (0xA782, "M", "ꞃ"), + (0xA783, "V"), + (0xA784, "M", "ꞅ"), + (0xA785, "V"), + (0xA786, "M", "ꞇ"), + (0xA787, "V"), + (0xA78B, "M", "ꞌ"), + (0xA78C, "V"), + (0xA78D, "M", "ɥ"), + (0xA78E, "V"), + (0xA790, "M", "ꞑ"), + (0xA791, "V"), + (0xA792, "M", "ꞓ"), + (0xA793, "V"), + (0xA796, "M", "ꞗ"), + (0xA797, "V"), + (0xA798, "M", "ꞙ"), + (0xA799, "V"), + (0xA79A, "M", "ꞛ"), + (0xA79B, "V"), + (0xA79C, "M", "ꞝ"), + (0xA79D, "V"), + (0xA79E, "M", "ꞟ"), + (0xA79F, "V"), + (0xA7A0, "M", "ꞡ"), + (0xA7A1, "V"), + (0xA7A2, "M", "ꞣ"), + (0xA7A3, "V"), + (0xA7A4, "M", "ꞥ"), + (0xA7A5, "V"), + (0xA7A6, "M", "ꞧ"), + (0xA7A7, "V"), + (0xA7A8, "M", "ꞩ"), + (0xA7A9, "V"), + (0xA7AA, "M", "ɦ"), + (0xA7AB, "M", "ɜ"), + (0xA7AC, "M", "ɡ"), + (0xA7AD, "M", "ɬ"), + (0xA7AE, "M", "ɪ"), + (0xA7AF, "V"), + (0xA7B0, "M", "ʞ"), + (0xA7B1, "M", "ʇ"), + (0xA7B2, "M", "ʝ"), + (0xA7B3, "M", "ꭓ"), + (0xA7B4, "M", "ꞵ"), + (0xA7B5, "V"), + (0xA7B6, "M", "ꞷ"), + (0xA7B7, "V"), + (0xA7B8, "M", "ꞹ"), + (0xA7B9, "V"), + (0xA7BA, "M", "ꞻ"), + (0xA7BB, "V"), + (0xA7BC, "M", "ꞽ"), + (0xA7BD, "V"), + (0xA7BE, "M", "ꞿ"), + (0xA7BF, "V"), + (0xA7C0, "M", "ꟁ"), + (0xA7C1, "V"), + (0xA7C2, "M", "ꟃ"), + (0xA7C3, "V"), + (0xA7C4, "M", "ꞔ"), + (0xA7C5, "M", "ʂ"), + (0xA7C6, "M", "ᶎ"), + (0xA7C7, "M", "ꟈ"), + (0xA7C8, "V"), + (0xA7C9, "M", "ꟊ"), + (0xA7CA, "V"), + (0xA7CB, "X"), + (0xA7D0, "M", "ꟑ"), + (0xA7D1, "V"), + (0xA7D2, "X"), + (0xA7D3, "V"), + (0xA7D4, "X"), + (0xA7D5, "V"), + (0xA7D6, "M", "ꟗ"), + (0xA7D7, "V"), + (0xA7D8, "M", "ꟙ"), + (0xA7D9, "V"), + (0xA7DA, "X"), + (0xA7F2, "M", "c"), + (0xA7F3, "M", "f"), + (0xA7F4, "M", "q"), + (0xA7F5, "M", "ꟶ"), + (0xA7F6, "V"), + (0xA7F8, "M", "ħ"), + (0xA7F9, "M", "œ"), + (0xA7FA, "V"), + (0xA82D, "X"), + (0xA830, "V"), + (0xA83A, "X"), + (0xA840, "V"), + (0xA878, "X"), + (0xA880, "V"), + (0xA8C6, "X"), + (0xA8CE, "V"), + (0xA8DA, "X"), + (0xA8E0, "V"), + (0xA954, "X"), + ] + + +def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xA95F, "V"), + (0xA97D, "X"), + (0xA980, "V"), + (0xA9CE, "X"), + (0xA9CF, "V"), + (0xA9DA, "X"), + (0xA9DE, "V"), + (0xA9FF, "X"), + (0xAA00, "V"), + (0xAA37, "X"), + (0xAA40, "V"), + (0xAA4E, "X"), + (0xAA50, "V"), + (0xAA5A, "X"), + (0xAA5C, "V"), + (0xAAC3, "X"), + (0xAADB, "V"), + (0xAAF7, "X"), + (0xAB01, "V"), + (0xAB07, "X"), + (0xAB09, "V"), + (0xAB0F, "X"), + (0xAB11, "V"), + (0xAB17, "X"), + (0xAB20, "V"), + (0xAB27, "X"), + (0xAB28, "V"), + (0xAB2F, "X"), + (0xAB30, "V"), + (0xAB5C, "M", "ꜧ"), + (0xAB5D, "M", "ꬷ"), + (0xAB5E, "M", "ɫ"), + (0xAB5F, "M", "ꭒ"), + (0xAB60, "V"), + (0xAB69, "M", "ʍ"), + (0xAB6A, "V"), + (0xAB6C, "X"), + (0xAB70, "M", "Ꭰ"), + (0xAB71, "M", "Ꭱ"), + (0xAB72, "M", "Ꭲ"), + (0xAB73, "M", "Ꭳ"), + (0xAB74, "M", "Ꭴ"), + (0xAB75, "M", "Ꭵ"), + (0xAB76, "M", "Ꭶ"), + (0xAB77, "M", "Ꭷ"), + (0xAB78, "M", "Ꭸ"), + (0xAB79, "M", "Ꭹ"), + (0xAB7A, "M", "Ꭺ"), + (0xAB7B, "M", "Ꭻ"), + (0xAB7C, "M", "Ꭼ"), + (0xAB7D, "M", "Ꭽ"), + (0xAB7E, "M", "Ꭾ"), + (0xAB7F, "M", "Ꭿ"), + (0xAB80, "M", "Ꮀ"), + (0xAB81, "M", "Ꮁ"), + (0xAB82, "M", "Ꮂ"), + (0xAB83, "M", "Ꮃ"), + (0xAB84, "M", "Ꮄ"), + (0xAB85, "M", "Ꮅ"), + (0xAB86, "M", "Ꮆ"), + (0xAB87, "M", "Ꮇ"), + (0xAB88, "M", "Ꮈ"), + (0xAB89, "M", "Ꮉ"), + (0xAB8A, "M", "Ꮊ"), + (0xAB8B, "M", "Ꮋ"), + (0xAB8C, "M", "Ꮌ"), + (0xAB8D, "M", "Ꮍ"), + (0xAB8E, "M", "Ꮎ"), + (0xAB8F, "M", "Ꮏ"), + (0xAB90, "M", "Ꮐ"), + (0xAB91, "M", "Ꮑ"), + (0xAB92, "M", "Ꮒ"), + (0xAB93, "M", "Ꮓ"), + (0xAB94, "M", "Ꮔ"), + (0xAB95, "M", "Ꮕ"), + (0xAB96, "M", "Ꮖ"), + (0xAB97, "M", "Ꮗ"), + (0xAB98, "M", "Ꮘ"), + (0xAB99, "M", "Ꮙ"), + (0xAB9A, "M", "Ꮚ"), + (0xAB9B, "M", "Ꮛ"), + (0xAB9C, "M", "Ꮜ"), + (0xAB9D, "M", "Ꮝ"), + (0xAB9E, "M", "Ꮞ"), + (0xAB9F, "M", "Ꮟ"), + (0xABA0, "M", "Ꮠ"), + (0xABA1, "M", "Ꮡ"), + (0xABA2, "M", "Ꮢ"), + (0xABA3, "M", "Ꮣ"), + (0xABA4, "M", "Ꮤ"), + (0xABA5, "M", "Ꮥ"), + (0xABA6, "M", "Ꮦ"), + (0xABA7, "M", "Ꮧ"), + (0xABA8, "M", "Ꮨ"), + (0xABA9, "M", "Ꮩ"), + (0xABAA, "M", "Ꮪ"), + (0xABAB, "M", "Ꮫ"), + (0xABAC, "M", "Ꮬ"), + (0xABAD, "M", "Ꮭ"), + (0xABAE, "M", "Ꮮ"), + ] + + +def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xABAF, "M", "Ꮯ"), + (0xABB0, "M", "Ꮰ"), + (0xABB1, "M", "Ꮱ"), + (0xABB2, "M", "Ꮲ"), + (0xABB3, "M", "Ꮳ"), + (0xABB4, "M", "Ꮴ"), + (0xABB5, "M", "Ꮵ"), + (0xABB6, "M", "Ꮶ"), + (0xABB7, "M", "Ꮷ"), + (0xABB8, "M", "Ꮸ"), + (0xABB9, "M", "Ꮹ"), + (0xABBA, "M", "Ꮺ"), + (0xABBB, "M", "Ꮻ"), + (0xABBC, "M", "Ꮼ"), + (0xABBD, "M", "Ꮽ"), + (0xABBE, "M", "Ꮾ"), + (0xABBF, "M", "Ꮿ"), + (0xABC0, "V"), + (0xABEE, "X"), + (0xABF0, "V"), + (0xABFA, "X"), + (0xAC00, "V"), + (0xD7A4, "X"), + (0xD7B0, "V"), + (0xD7C7, "X"), + (0xD7CB, "V"), + (0xD7FC, "X"), + (0xF900, "M", "豈"), + (0xF901, "M", "更"), + (0xF902, "M", "車"), + (0xF903, "M", "賈"), + (0xF904, "M", "滑"), + (0xF905, "M", "串"), + (0xF906, "M", "句"), + (0xF907, "M", "龜"), + (0xF909, "M", "契"), + (0xF90A, "M", "金"), + (0xF90B, "M", "喇"), + (0xF90C, "M", "奈"), + (0xF90D, "M", "懶"), + (0xF90E, "M", "癩"), + (0xF90F, "M", "羅"), + (0xF910, "M", "蘿"), + (0xF911, "M", "螺"), + (0xF912, "M", "裸"), + (0xF913, "M", "邏"), + (0xF914, "M", "樂"), + (0xF915, "M", "洛"), + (0xF916, "M", "烙"), + (0xF917, "M", "珞"), + (0xF918, "M", "落"), + (0xF919, "M", "酪"), + (0xF91A, "M", "駱"), + (0xF91B, "M", "亂"), + (0xF91C, "M", "卵"), + (0xF91D, "M", "欄"), + (0xF91E, "M", "爛"), + (0xF91F, "M", "蘭"), + (0xF920, "M", "鸞"), + (0xF921, "M", "嵐"), + (0xF922, "M", "濫"), + (0xF923, "M", "藍"), + (0xF924, "M", "襤"), + (0xF925, "M", "拉"), + (0xF926, "M", "臘"), + (0xF927, "M", "蠟"), + (0xF928, "M", "廊"), + (0xF929, "M", "朗"), + (0xF92A, "M", "浪"), + (0xF92B, "M", "狼"), + (0xF92C, "M", "郎"), + (0xF92D, "M", "來"), + (0xF92E, "M", "冷"), + (0xF92F, "M", "勞"), + (0xF930, "M", "擄"), + (0xF931, "M", "櫓"), + (0xF932, "M", "爐"), + (0xF933, "M", "盧"), + (0xF934, "M", "老"), + (0xF935, "M", "蘆"), + (0xF936, "M", "虜"), + (0xF937, "M", "路"), + (0xF938, "M", "露"), + (0xF939, "M", "魯"), + (0xF93A, "M", "鷺"), + (0xF93B, "M", "碌"), + (0xF93C, "M", "祿"), + (0xF93D, "M", "綠"), + (0xF93E, "M", "菉"), + (0xF93F, "M", "錄"), + (0xF940, "M", "鹿"), + (0xF941, "M", "論"), + (0xF942, "M", "壟"), + (0xF943, "M", "弄"), + (0xF944, "M", "籠"), + (0xF945, "M", "聾"), + (0xF946, "M", "牢"), + (0xF947, "M", "磊"), + (0xF948, "M", "賂"), + (0xF949, "M", "雷"), + ] + + +def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xF94A, "M", "壘"), + (0xF94B, "M", "屢"), + (0xF94C, "M", "樓"), + (0xF94D, "M", "淚"), + (0xF94E, "M", "漏"), + (0xF94F, "M", "累"), + (0xF950, "M", "縷"), + (0xF951, "M", "陋"), + (0xF952, "M", "勒"), + (0xF953, "M", "肋"), + (0xF954, "M", "凜"), + (0xF955, "M", "凌"), + (0xF956, "M", "稜"), + (0xF957, "M", "綾"), + (0xF958, "M", "菱"), + (0xF959, "M", "陵"), + (0xF95A, "M", "讀"), + (0xF95B, "M", "拏"), + (0xF95C, "M", "樂"), + (0xF95D, "M", "諾"), + (0xF95E, "M", "丹"), + (0xF95F, "M", "寧"), + (0xF960, "M", "怒"), + (0xF961, "M", "率"), + (0xF962, "M", "異"), + (0xF963, "M", "北"), + (0xF964, "M", "磻"), + (0xF965, "M", "便"), + (0xF966, "M", "復"), + (0xF967, "M", "不"), + (0xF968, "M", "泌"), + (0xF969, "M", "數"), + (0xF96A, "M", "索"), + (0xF96B, "M", "參"), + (0xF96C, "M", "塞"), + (0xF96D, "M", "省"), + (0xF96E, "M", "葉"), + (0xF96F, "M", "說"), + (0xF970, "M", "殺"), + (0xF971, "M", "辰"), + (0xF972, "M", "沈"), + (0xF973, "M", "拾"), + (0xF974, "M", "若"), + (0xF975, "M", "掠"), + (0xF976, "M", "略"), + (0xF977, "M", "亮"), + (0xF978, "M", "兩"), + (0xF979, "M", "凉"), + (0xF97A, "M", "梁"), + (0xF97B, "M", "糧"), + (0xF97C, "M", "良"), + (0xF97D, "M", "諒"), + (0xF97E, "M", "量"), + (0xF97F, "M", "勵"), + (0xF980, "M", "呂"), + (0xF981, "M", "女"), + (0xF982, "M", "廬"), + (0xF983, "M", "旅"), + (0xF984, "M", "濾"), + (0xF985, "M", "礪"), + (0xF986, "M", "閭"), + (0xF987, "M", "驪"), + (0xF988, "M", "麗"), + (0xF989, "M", "黎"), + (0xF98A, "M", "力"), + (0xF98B, "M", "曆"), + (0xF98C, "M", "歷"), + (0xF98D, "M", "轢"), + (0xF98E, "M", "年"), + (0xF98F, "M", "憐"), + (0xF990, "M", "戀"), + (0xF991, "M", "撚"), + (0xF992, "M", "漣"), + (0xF993, "M", "煉"), + (0xF994, "M", "璉"), + (0xF995, "M", "秊"), + (0xF996, "M", "練"), + (0xF997, "M", "聯"), + (0xF998, "M", "輦"), + (0xF999, "M", "蓮"), + (0xF99A, "M", "連"), + (0xF99B, "M", "鍊"), + (0xF99C, "M", "列"), + (0xF99D, "M", "劣"), + (0xF99E, "M", "咽"), + (0xF99F, "M", "烈"), + (0xF9A0, "M", "裂"), + (0xF9A1, "M", "說"), + (0xF9A2, "M", "廉"), + (0xF9A3, "M", "念"), + (0xF9A4, "M", "捻"), + (0xF9A5, "M", "殮"), + (0xF9A6, "M", "簾"), + (0xF9A7, "M", "獵"), + (0xF9A8, "M", "令"), + (0xF9A9, "M", "囹"), + (0xF9AA, "M", "寧"), + (0xF9AB, "M", "嶺"), + (0xF9AC, "M", "怜"), + (0xF9AD, "M", "玲"), + ] + + +def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xF9AE, "M", "瑩"), + (0xF9AF, "M", "羚"), + (0xF9B0, "M", "聆"), + (0xF9B1, "M", "鈴"), + (0xF9B2, "M", "零"), + (0xF9B3, "M", "靈"), + (0xF9B4, "M", "領"), + (0xF9B5, "M", "例"), + (0xF9B6, "M", "禮"), + (0xF9B7, "M", "醴"), + (0xF9B8, "M", "隸"), + (0xF9B9, "M", "惡"), + (0xF9BA, "M", "了"), + (0xF9BB, "M", "僚"), + (0xF9BC, "M", "寮"), + (0xF9BD, "M", "尿"), + (0xF9BE, "M", "料"), + (0xF9BF, "M", "樂"), + (0xF9C0, "M", "燎"), + (0xF9C1, "M", "療"), + (0xF9C2, "M", "蓼"), + (0xF9C3, "M", "遼"), + (0xF9C4, "M", "龍"), + (0xF9C5, "M", "暈"), + (0xF9C6, "M", "阮"), + (0xF9C7, "M", "劉"), + (0xF9C8, "M", "杻"), + (0xF9C9, "M", "柳"), + (0xF9CA, "M", "流"), + (0xF9CB, "M", "溜"), + (0xF9CC, "M", "琉"), + (0xF9CD, "M", "留"), + (0xF9CE, "M", "硫"), + (0xF9CF, "M", "紐"), + (0xF9D0, "M", "類"), + (0xF9D1, "M", "六"), + (0xF9D2, "M", "戮"), + (0xF9D3, "M", "陸"), + (0xF9D4, "M", "倫"), + (0xF9D5, "M", "崙"), + (0xF9D6, "M", "淪"), + (0xF9D7, "M", "輪"), + (0xF9D8, "M", "律"), + (0xF9D9, "M", "慄"), + (0xF9DA, "M", "栗"), + (0xF9DB, "M", "率"), + (0xF9DC, "M", "隆"), + (0xF9DD, "M", "利"), + (0xF9DE, "M", "吏"), + (0xF9DF, "M", "履"), + (0xF9E0, "M", "易"), + (0xF9E1, "M", "李"), + (0xF9E2, "M", "梨"), + (0xF9E3, "M", "泥"), + (0xF9E4, "M", "理"), + (0xF9E5, "M", "痢"), + (0xF9E6, "M", "罹"), + (0xF9E7, "M", "裏"), + (0xF9E8, "M", "裡"), + (0xF9E9, "M", "里"), + (0xF9EA, "M", "離"), + (0xF9EB, "M", "匿"), + (0xF9EC, "M", "溺"), + (0xF9ED, "M", "吝"), + (0xF9EE, "M", "燐"), + (0xF9EF, "M", "璘"), + (0xF9F0, "M", "藺"), + (0xF9F1, "M", "隣"), + (0xF9F2, "M", "鱗"), + (0xF9F3, "M", "麟"), + (0xF9F4, "M", "林"), + (0xF9F5, "M", "淋"), + (0xF9F6, "M", "臨"), + (0xF9F7, "M", "立"), + (0xF9F8, "M", "笠"), + (0xF9F9, "M", "粒"), + (0xF9FA, "M", "狀"), + (0xF9FB, "M", "炙"), + (0xF9FC, "M", "識"), + (0xF9FD, "M", "什"), + (0xF9FE, "M", "茶"), + (0xF9FF, "M", "刺"), + (0xFA00, "M", "切"), + (0xFA01, "M", "度"), + (0xFA02, "M", "拓"), + (0xFA03, "M", "糖"), + (0xFA04, "M", "宅"), + (0xFA05, "M", "洞"), + (0xFA06, "M", "暴"), + (0xFA07, "M", "輻"), + (0xFA08, "M", "行"), + (0xFA09, "M", "降"), + (0xFA0A, "M", "見"), + (0xFA0B, "M", "廓"), + (0xFA0C, "M", "兀"), + (0xFA0D, "M", "嗀"), + (0xFA0E, "V"), + (0xFA10, "M", "塚"), + (0xFA11, "V"), + (0xFA12, "M", "晴"), + ] + + +def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFA13, "V"), + (0xFA15, "M", "凞"), + (0xFA16, "M", "猪"), + (0xFA17, "M", "益"), + (0xFA18, "M", "礼"), + (0xFA19, "M", "神"), + (0xFA1A, "M", "祥"), + (0xFA1B, "M", "福"), + (0xFA1C, "M", "靖"), + (0xFA1D, "M", "精"), + (0xFA1E, "M", "羽"), + (0xFA1F, "V"), + (0xFA20, "M", "蘒"), + (0xFA21, "V"), + (0xFA22, "M", "諸"), + (0xFA23, "V"), + (0xFA25, "M", "逸"), + (0xFA26, "M", "都"), + (0xFA27, "V"), + (0xFA2A, "M", "飯"), + (0xFA2B, "M", "飼"), + (0xFA2C, "M", "館"), + (0xFA2D, "M", "鶴"), + (0xFA2E, "M", "郞"), + (0xFA2F, "M", "隷"), + (0xFA30, "M", "侮"), + (0xFA31, "M", "僧"), + (0xFA32, "M", "免"), + (0xFA33, "M", "勉"), + (0xFA34, "M", "勤"), + (0xFA35, "M", "卑"), + (0xFA36, "M", "喝"), + (0xFA37, "M", "嘆"), + (0xFA38, "M", "器"), + (0xFA39, "M", "塀"), + (0xFA3A, "M", "墨"), + (0xFA3B, "M", "層"), + (0xFA3C, "M", "屮"), + (0xFA3D, "M", "悔"), + (0xFA3E, "M", "慨"), + (0xFA3F, "M", "憎"), + (0xFA40, "M", "懲"), + (0xFA41, "M", "敏"), + (0xFA42, "M", "既"), + (0xFA43, "M", "暑"), + (0xFA44, "M", "梅"), + (0xFA45, "M", "海"), + (0xFA46, "M", "渚"), + (0xFA47, "M", "漢"), + (0xFA48, "M", "煮"), + (0xFA49, "M", "爫"), + (0xFA4A, "M", "琢"), + (0xFA4B, "M", "碑"), + (0xFA4C, "M", "社"), + (0xFA4D, "M", "祉"), + (0xFA4E, "M", "祈"), + (0xFA4F, "M", "祐"), + (0xFA50, "M", "祖"), + (0xFA51, "M", "祝"), + (0xFA52, "M", "禍"), + (0xFA53, "M", "禎"), + (0xFA54, "M", "穀"), + (0xFA55, "M", "突"), + (0xFA56, "M", "節"), + (0xFA57, "M", "練"), + (0xFA58, "M", "縉"), + (0xFA59, "M", "繁"), + (0xFA5A, "M", "署"), + (0xFA5B, "M", "者"), + (0xFA5C, "M", "臭"), + (0xFA5D, "M", "艹"), + (0xFA5F, "M", "著"), + (0xFA60, "M", "褐"), + (0xFA61, "M", "視"), + (0xFA62, "M", "謁"), + (0xFA63, "M", "謹"), + (0xFA64, "M", "賓"), + (0xFA65, "M", "贈"), + (0xFA66, "M", "辶"), + (0xFA67, "M", "逸"), + (0xFA68, "M", "難"), + (0xFA69, "M", "響"), + (0xFA6A, "M", "頻"), + (0xFA6B, "M", "恵"), + (0xFA6C, "M", "𤋮"), + (0xFA6D, "M", "舘"), + (0xFA6E, "X"), + (0xFA70, "M", "並"), + (0xFA71, "M", "况"), + (0xFA72, "M", "全"), + (0xFA73, "M", "侀"), + (0xFA74, "M", "充"), + (0xFA75, "M", "冀"), + (0xFA76, "M", "勇"), + (0xFA77, "M", "勺"), + (0xFA78, "M", "喝"), + (0xFA79, "M", "啕"), + (0xFA7A, "M", "喙"), + (0xFA7B, "M", "嗢"), + (0xFA7C, "M", "塚"), + ] + + +def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFA7D, "M", "墳"), + (0xFA7E, "M", "奄"), + (0xFA7F, "M", "奔"), + (0xFA80, "M", "婢"), + (0xFA81, "M", "嬨"), + (0xFA82, "M", "廒"), + (0xFA83, "M", "廙"), + (0xFA84, "M", "彩"), + (0xFA85, "M", "徭"), + (0xFA86, "M", "惘"), + (0xFA87, "M", "慎"), + (0xFA88, "M", "愈"), + (0xFA89, "M", "憎"), + (0xFA8A, "M", "慠"), + (0xFA8B, "M", "懲"), + (0xFA8C, "M", "戴"), + (0xFA8D, "M", "揄"), + (0xFA8E, "M", "搜"), + (0xFA8F, "M", "摒"), + (0xFA90, "M", "敖"), + (0xFA91, "M", "晴"), + (0xFA92, "M", "朗"), + (0xFA93, "M", "望"), + (0xFA94, "M", "杖"), + (0xFA95, "M", "歹"), + (0xFA96, "M", "殺"), + (0xFA97, "M", "流"), + (0xFA98, "M", "滛"), + (0xFA99, "M", "滋"), + (0xFA9A, "M", "漢"), + (0xFA9B, "M", "瀞"), + (0xFA9C, "M", "煮"), + (0xFA9D, "M", "瞧"), + (0xFA9E, "M", "爵"), + (0xFA9F, "M", "犯"), + (0xFAA0, "M", "猪"), + (0xFAA1, "M", "瑱"), + (0xFAA2, "M", "甆"), + (0xFAA3, "M", "画"), + (0xFAA4, "M", "瘝"), + (0xFAA5, "M", "瘟"), + (0xFAA6, "M", "益"), + (0xFAA7, "M", "盛"), + (0xFAA8, "M", "直"), + (0xFAA9, "M", "睊"), + (0xFAAA, "M", "着"), + (0xFAAB, "M", "磌"), + (0xFAAC, "M", "窱"), + (0xFAAD, "M", "節"), + (0xFAAE, "M", "类"), + (0xFAAF, "M", "絛"), + (0xFAB0, "M", "練"), + (0xFAB1, "M", "缾"), + (0xFAB2, "M", "者"), + (0xFAB3, "M", "荒"), + (0xFAB4, "M", "華"), + (0xFAB5, "M", "蝹"), + (0xFAB6, "M", "襁"), + (0xFAB7, "M", "覆"), + (0xFAB8, "M", "視"), + (0xFAB9, "M", "調"), + (0xFABA, "M", "諸"), + (0xFABB, "M", "請"), + (0xFABC, "M", "謁"), + (0xFABD, "M", "諾"), + (0xFABE, "M", "諭"), + (0xFABF, "M", "謹"), + (0xFAC0, "M", "變"), + (0xFAC1, "M", "贈"), + (0xFAC2, "M", "輸"), + (0xFAC3, "M", "遲"), + (0xFAC4, "M", "醙"), + (0xFAC5, "M", "鉶"), + (0xFAC6, "M", "陼"), + (0xFAC7, "M", "難"), + (0xFAC8, "M", "靖"), + (0xFAC9, "M", "韛"), + (0xFACA, "M", "響"), + (0xFACB, "M", "頋"), + (0xFACC, "M", "頻"), + (0xFACD, "M", "鬒"), + (0xFACE, "M", "龜"), + (0xFACF, "M", "𢡊"), + (0xFAD0, "M", "𢡄"), + (0xFAD1, "M", "𣏕"), + (0xFAD2, "M", "㮝"), + (0xFAD3, "M", "䀘"), + (0xFAD4, "M", "䀹"), + (0xFAD5, "M", "𥉉"), + (0xFAD6, "M", "𥳐"), + (0xFAD7, "M", "𧻓"), + (0xFAD8, "M", "齃"), + (0xFAD9, "M", "龎"), + (0xFADA, "X"), + (0xFB00, "M", "ff"), + (0xFB01, "M", "fi"), + (0xFB02, "M", "fl"), + (0xFB03, "M", "ffi"), + (0xFB04, "M", "ffl"), + (0xFB05, "M", "st"), + ] + + +def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFB07, "X"), + (0xFB13, "M", "մն"), + (0xFB14, "M", "մե"), + (0xFB15, "M", "մի"), + (0xFB16, "M", "վն"), + (0xFB17, "M", "մխ"), + (0xFB18, "X"), + (0xFB1D, "M", "יִ"), + (0xFB1E, "V"), + (0xFB1F, "M", "ײַ"), + (0xFB20, "M", "ע"), + (0xFB21, "M", "א"), + (0xFB22, "M", "ד"), + (0xFB23, "M", "ה"), + (0xFB24, "M", "כ"), + (0xFB25, "M", "ל"), + (0xFB26, "M", "ם"), + (0xFB27, "M", "ר"), + (0xFB28, "M", "ת"), + (0xFB29, "3", "+"), + (0xFB2A, "M", "שׁ"), + (0xFB2B, "M", "שׂ"), + (0xFB2C, "M", "שּׁ"), + (0xFB2D, "M", "שּׂ"), + (0xFB2E, "M", "אַ"), + (0xFB2F, "M", "אָ"), + (0xFB30, "M", "אּ"), + (0xFB31, "M", "בּ"), + (0xFB32, "M", "גּ"), + (0xFB33, "M", "דּ"), + (0xFB34, "M", "הּ"), + (0xFB35, "M", "וּ"), + (0xFB36, "M", "זּ"), + (0xFB37, "X"), + (0xFB38, "M", "טּ"), + (0xFB39, "M", "יּ"), + (0xFB3A, "M", "ךּ"), + (0xFB3B, "M", "כּ"), + (0xFB3C, "M", "לּ"), + (0xFB3D, "X"), + (0xFB3E, "M", "מּ"), + (0xFB3F, "X"), + (0xFB40, "M", "נּ"), + (0xFB41, "M", "סּ"), + (0xFB42, "X"), + (0xFB43, "M", "ףּ"), + (0xFB44, "M", "פּ"), + (0xFB45, "X"), + (0xFB46, "M", "צּ"), + (0xFB47, "M", "קּ"), + (0xFB48, "M", "רּ"), + (0xFB49, "M", "שּ"), + (0xFB4A, "M", "תּ"), + (0xFB4B, "M", "וֹ"), + (0xFB4C, "M", "בֿ"), + (0xFB4D, "M", "כֿ"), + (0xFB4E, "M", "פֿ"), + (0xFB4F, "M", "אל"), + (0xFB50, "M", "ٱ"), + (0xFB52, "M", "ٻ"), + (0xFB56, "M", "پ"), + (0xFB5A, "M", "ڀ"), + (0xFB5E, "M", "ٺ"), + (0xFB62, "M", "ٿ"), + (0xFB66, "M", "ٹ"), + (0xFB6A, "M", "ڤ"), + (0xFB6E, "M", "ڦ"), + (0xFB72, "M", "ڄ"), + (0xFB76, "M", "ڃ"), + (0xFB7A, "M", "چ"), + (0xFB7E, "M", "ڇ"), + (0xFB82, "M", "ڍ"), + (0xFB84, "M", "ڌ"), + (0xFB86, "M", "ڎ"), + (0xFB88, "M", "ڈ"), + (0xFB8A, "M", "ژ"), + (0xFB8C, "M", "ڑ"), + (0xFB8E, "M", "ک"), + (0xFB92, "M", "گ"), + (0xFB96, "M", "ڳ"), + (0xFB9A, "M", "ڱ"), + (0xFB9E, "M", "ں"), + (0xFBA0, "M", "ڻ"), + (0xFBA4, "M", "ۀ"), + (0xFBA6, "M", "ہ"), + (0xFBAA, "M", "ھ"), + (0xFBAE, "M", "ے"), + (0xFBB0, "M", "ۓ"), + (0xFBB2, "V"), + (0xFBC3, "X"), + (0xFBD3, "M", "ڭ"), + (0xFBD7, "M", "ۇ"), + (0xFBD9, "M", "ۆ"), + (0xFBDB, "M", "ۈ"), + (0xFBDD, "M", "ۇٴ"), + (0xFBDE, "M", "ۋ"), + (0xFBE0, "M", "ۅ"), + (0xFBE2, "M", "ۉ"), + (0xFBE4, "M", "ې"), + (0xFBE8, "M", "ى"), + ] + + +def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFBEA, "M", "ئا"), + (0xFBEC, "M", "ئە"), + (0xFBEE, "M", "ئو"), + (0xFBF0, "M", "ئۇ"), + (0xFBF2, "M", "ئۆ"), + (0xFBF4, "M", "ئۈ"), + (0xFBF6, "M", "ئې"), + (0xFBF9, "M", "ئى"), + (0xFBFC, "M", "ی"), + (0xFC00, "M", "ئج"), + (0xFC01, "M", "ئح"), + (0xFC02, "M", "ئم"), + (0xFC03, "M", "ئى"), + (0xFC04, "M", "ئي"), + (0xFC05, "M", "بج"), + (0xFC06, "M", "بح"), + (0xFC07, "M", "بخ"), + (0xFC08, "M", "بم"), + (0xFC09, "M", "بى"), + (0xFC0A, "M", "بي"), + (0xFC0B, "M", "تج"), + (0xFC0C, "M", "تح"), + (0xFC0D, "M", "تخ"), + (0xFC0E, "M", "تم"), + (0xFC0F, "M", "تى"), + (0xFC10, "M", "تي"), + (0xFC11, "M", "ثج"), + (0xFC12, "M", "ثم"), + (0xFC13, "M", "ثى"), + (0xFC14, "M", "ثي"), + (0xFC15, "M", "جح"), + (0xFC16, "M", "جم"), + (0xFC17, "M", "حج"), + (0xFC18, "M", "حم"), + (0xFC19, "M", "خج"), + (0xFC1A, "M", "خح"), + (0xFC1B, "M", "خم"), + (0xFC1C, "M", "سج"), + (0xFC1D, "M", "سح"), + (0xFC1E, "M", "سخ"), + (0xFC1F, "M", "سم"), + (0xFC20, "M", "صح"), + (0xFC21, "M", "صم"), + (0xFC22, "M", "ضج"), + (0xFC23, "M", "ضح"), + (0xFC24, "M", "ضخ"), + (0xFC25, "M", "ضم"), + (0xFC26, "M", "طح"), + (0xFC27, "M", "طم"), + (0xFC28, "M", "ظم"), + (0xFC29, "M", "عج"), + (0xFC2A, "M", "عم"), + (0xFC2B, "M", "غج"), + (0xFC2C, "M", "غم"), + (0xFC2D, "M", "فج"), + (0xFC2E, "M", "فح"), + (0xFC2F, "M", "فخ"), + (0xFC30, "M", "فم"), + (0xFC31, "M", "فى"), + (0xFC32, "M", "في"), + (0xFC33, "M", "قح"), + (0xFC34, "M", "قم"), + (0xFC35, "M", "قى"), + (0xFC36, "M", "قي"), + (0xFC37, "M", "كا"), + (0xFC38, "M", "كج"), + (0xFC39, "M", "كح"), + (0xFC3A, "M", "كخ"), + (0xFC3B, "M", "كل"), + (0xFC3C, "M", "كم"), + (0xFC3D, "M", "كى"), + (0xFC3E, "M", "كي"), + (0xFC3F, "M", "لج"), + (0xFC40, "M", "لح"), + (0xFC41, "M", "لخ"), + (0xFC42, "M", "لم"), + (0xFC43, "M", "لى"), + (0xFC44, "M", "لي"), + (0xFC45, "M", "مج"), + (0xFC46, "M", "مح"), + (0xFC47, "M", "مخ"), + (0xFC48, "M", "مم"), + (0xFC49, "M", "مى"), + (0xFC4A, "M", "مي"), + (0xFC4B, "M", "نج"), + (0xFC4C, "M", "نح"), + (0xFC4D, "M", "نخ"), + (0xFC4E, "M", "نم"), + (0xFC4F, "M", "نى"), + (0xFC50, "M", "ني"), + (0xFC51, "M", "هج"), + (0xFC52, "M", "هم"), + (0xFC53, "M", "هى"), + (0xFC54, "M", "هي"), + (0xFC55, "M", "يج"), + (0xFC56, "M", "يح"), + (0xFC57, "M", "يخ"), + (0xFC58, "M", "يم"), + (0xFC59, "M", "يى"), + (0xFC5A, "M", "يي"), + ] + + +def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFC5B, "M", "ذٰ"), + (0xFC5C, "M", "رٰ"), + (0xFC5D, "M", "ىٰ"), + (0xFC5E, "3", " ٌّ"), + (0xFC5F, "3", " ٍّ"), + (0xFC60, "3", " َّ"), + (0xFC61, "3", " ُّ"), + (0xFC62, "3", " ِّ"), + (0xFC63, "3", " ّٰ"), + (0xFC64, "M", "ئر"), + (0xFC65, "M", "ئز"), + (0xFC66, "M", "ئم"), + (0xFC67, "M", "ئن"), + (0xFC68, "M", "ئى"), + (0xFC69, "M", "ئي"), + (0xFC6A, "M", "بر"), + (0xFC6B, "M", "بز"), + (0xFC6C, "M", "بم"), + (0xFC6D, "M", "بن"), + (0xFC6E, "M", "بى"), + (0xFC6F, "M", "بي"), + (0xFC70, "M", "تر"), + (0xFC71, "M", "تز"), + (0xFC72, "M", "تم"), + (0xFC73, "M", "تن"), + (0xFC74, "M", "تى"), + (0xFC75, "M", "تي"), + (0xFC76, "M", "ثر"), + (0xFC77, "M", "ثز"), + (0xFC78, "M", "ثم"), + (0xFC79, "M", "ثن"), + (0xFC7A, "M", "ثى"), + (0xFC7B, "M", "ثي"), + (0xFC7C, "M", "فى"), + (0xFC7D, "M", "في"), + (0xFC7E, "M", "قى"), + (0xFC7F, "M", "قي"), + (0xFC80, "M", "كا"), + (0xFC81, "M", "كل"), + (0xFC82, "M", "كم"), + (0xFC83, "M", "كى"), + (0xFC84, "M", "كي"), + (0xFC85, "M", "لم"), + (0xFC86, "M", "لى"), + (0xFC87, "M", "لي"), + (0xFC88, "M", "ما"), + (0xFC89, "M", "مم"), + (0xFC8A, "M", "نر"), + (0xFC8B, "M", "نز"), + (0xFC8C, "M", "نم"), + (0xFC8D, "M", "نن"), + (0xFC8E, "M", "نى"), + (0xFC8F, "M", "ني"), + (0xFC90, "M", "ىٰ"), + (0xFC91, "M", "ير"), + (0xFC92, "M", "يز"), + (0xFC93, "M", "يم"), + (0xFC94, "M", "ين"), + (0xFC95, "M", "يى"), + (0xFC96, "M", "يي"), + (0xFC97, "M", "ئج"), + (0xFC98, "M", "ئح"), + (0xFC99, "M", "ئخ"), + (0xFC9A, "M", "ئم"), + (0xFC9B, "M", "ئه"), + (0xFC9C, "M", "بج"), + (0xFC9D, "M", "بح"), + (0xFC9E, "M", "بخ"), + (0xFC9F, "M", "بم"), + (0xFCA0, "M", "به"), + (0xFCA1, "M", "تج"), + (0xFCA2, "M", "تح"), + (0xFCA3, "M", "تخ"), + (0xFCA4, "M", "تم"), + (0xFCA5, "M", "ته"), + (0xFCA6, "M", "ثم"), + (0xFCA7, "M", "جح"), + (0xFCA8, "M", "جم"), + (0xFCA9, "M", "حج"), + (0xFCAA, "M", "حم"), + (0xFCAB, "M", "خج"), + (0xFCAC, "M", "خم"), + (0xFCAD, "M", "سج"), + (0xFCAE, "M", "سح"), + (0xFCAF, "M", "سخ"), + (0xFCB0, "M", "سم"), + (0xFCB1, "M", "صح"), + (0xFCB2, "M", "صخ"), + (0xFCB3, "M", "صم"), + (0xFCB4, "M", "ضج"), + (0xFCB5, "M", "ضح"), + (0xFCB6, "M", "ضخ"), + (0xFCB7, "M", "ضم"), + (0xFCB8, "M", "طح"), + (0xFCB9, "M", "ظم"), + (0xFCBA, "M", "عج"), + (0xFCBB, "M", "عم"), + (0xFCBC, "M", "غج"), + (0xFCBD, "M", "غم"), + (0xFCBE, "M", "فج"), + ] + + +def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFCBF, "M", "فح"), + (0xFCC0, "M", "فخ"), + (0xFCC1, "M", "فم"), + (0xFCC2, "M", "قح"), + (0xFCC3, "M", "قم"), + (0xFCC4, "M", "كج"), + (0xFCC5, "M", "كح"), + (0xFCC6, "M", "كخ"), + (0xFCC7, "M", "كل"), + (0xFCC8, "M", "كم"), + (0xFCC9, "M", "لج"), + (0xFCCA, "M", "لح"), + (0xFCCB, "M", "لخ"), + (0xFCCC, "M", "لم"), + (0xFCCD, "M", "له"), + (0xFCCE, "M", "مج"), + (0xFCCF, "M", "مح"), + (0xFCD0, "M", "مخ"), + (0xFCD1, "M", "مم"), + (0xFCD2, "M", "نج"), + (0xFCD3, "M", "نح"), + (0xFCD4, "M", "نخ"), + (0xFCD5, "M", "نم"), + (0xFCD6, "M", "نه"), + (0xFCD7, "M", "هج"), + (0xFCD8, "M", "هم"), + (0xFCD9, "M", "هٰ"), + (0xFCDA, "M", "يج"), + (0xFCDB, "M", "يح"), + (0xFCDC, "M", "يخ"), + (0xFCDD, "M", "يم"), + (0xFCDE, "M", "يه"), + (0xFCDF, "M", "ئم"), + (0xFCE0, "M", "ئه"), + (0xFCE1, "M", "بم"), + (0xFCE2, "M", "به"), + (0xFCE3, "M", "تم"), + (0xFCE4, "M", "ته"), + (0xFCE5, "M", "ثم"), + (0xFCE6, "M", "ثه"), + (0xFCE7, "M", "سم"), + (0xFCE8, "M", "سه"), + (0xFCE9, "M", "شم"), + (0xFCEA, "M", "شه"), + (0xFCEB, "M", "كل"), + (0xFCEC, "M", "كم"), + (0xFCED, "M", "لم"), + (0xFCEE, "M", "نم"), + (0xFCEF, "M", "نه"), + (0xFCF0, "M", "يم"), + (0xFCF1, "M", "يه"), + (0xFCF2, "M", "ـَّ"), + (0xFCF3, "M", "ـُّ"), + (0xFCF4, "M", "ـِّ"), + (0xFCF5, "M", "طى"), + (0xFCF6, "M", "طي"), + (0xFCF7, "M", "عى"), + (0xFCF8, "M", "عي"), + (0xFCF9, "M", "غى"), + (0xFCFA, "M", "غي"), + (0xFCFB, "M", "سى"), + (0xFCFC, "M", "سي"), + (0xFCFD, "M", "شى"), + (0xFCFE, "M", "شي"), + (0xFCFF, "M", "حى"), + (0xFD00, "M", "حي"), + (0xFD01, "M", "جى"), + (0xFD02, "M", "جي"), + (0xFD03, "M", "خى"), + (0xFD04, "M", "خي"), + (0xFD05, "M", "صى"), + (0xFD06, "M", "صي"), + (0xFD07, "M", "ضى"), + (0xFD08, "M", "ضي"), + (0xFD09, "M", "شج"), + (0xFD0A, "M", "شح"), + (0xFD0B, "M", "شخ"), + (0xFD0C, "M", "شم"), + (0xFD0D, "M", "شر"), + (0xFD0E, "M", "سر"), + (0xFD0F, "M", "صر"), + (0xFD10, "M", "ضر"), + (0xFD11, "M", "طى"), + (0xFD12, "M", "طي"), + (0xFD13, "M", "عى"), + (0xFD14, "M", "عي"), + (0xFD15, "M", "غى"), + (0xFD16, "M", "غي"), + (0xFD17, "M", "سى"), + (0xFD18, "M", "سي"), + (0xFD19, "M", "شى"), + (0xFD1A, "M", "شي"), + (0xFD1B, "M", "حى"), + (0xFD1C, "M", "حي"), + (0xFD1D, "M", "جى"), + (0xFD1E, "M", "جي"), + (0xFD1F, "M", "خى"), + (0xFD20, "M", "خي"), + (0xFD21, "M", "صى"), + (0xFD22, "M", "صي"), + ] + + +def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFD23, "M", "ضى"), + (0xFD24, "M", "ضي"), + (0xFD25, "M", "شج"), + (0xFD26, "M", "شح"), + (0xFD27, "M", "شخ"), + (0xFD28, "M", "شم"), + (0xFD29, "M", "شر"), + (0xFD2A, "M", "سر"), + (0xFD2B, "M", "صر"), + (0xFD2C, "M", "ضر"), + (0xFD2D, "M", "شج"), + (0xFD2E, "M", "شح"), + (0xFD2F, "M", "شخ"), + (0xFD30, "M", "شم"), + (0xFD31, "M", "سه"), + (0xFD32, "M", "شه"), + (0xFD33, "M", "طم"), + (0xFD34, "M", "سج"), + (0xFD35, "M", "سح"), + (0xFD36, "M", "سخ"), + (0xFD37, "M", "شج"), + (0xFD38, "M", "شح"), + (0xFD39, "M", "شخ"), + (0xFD3A, "M", "طم"), + (0xFD3B, "M", "ظم"), + (0xFD3C, "M", "اً"), + (0xFD3E, "V"), + (0xFD50, "M", "تجم"), + (0xFD51, "M", "تحج"), + (0xFD53, "M", "تحم"), + (0xFD54, "M", "تخم"), + (0xFD55, "M", "تمج"), + (0xFD56, "M", "تمح"), + (0xFD57, "M", "تمخ"), + (0xFD58, "M", "جمح"), + (0xFD5A, "M", "حمي"), + (0xFD5B, "M", "حمى"), + (0xFD5C, "M", "سحج"), + (0xFD5D, "M", "سجح"), + (0xFD5E, "M", "سجى"), + (0xFD5F, "M", "سمح"), + (0xFD61, "M", "سمج"), + (0xFD62, "M", "سمم"), + (0xFD64, "M", "صحح"), + (0xFD66, "M", "صمم"), + (0xFD67, "M", "شحم"), + (0xFD69, "M", "شجي"), + (0xFD6A, "M", "شمخ"), + (0xFD6C, "M", "شمم"), + (0xFD6E, "M", "ضحى"), + (0xFD6F, "M", "ضخم"), + (0xFD71, "M", "طمح"), + (0xFD73, "M", "طمم"), + (0xFD74, "M", "طمي"), + (0xFD75, "M", "عجم"), + (0xFD76, "M", "عمم"), + (0xFD78, "M", "عمى"), + (0xFD79, "M", "غمم"), + (0xFD7A, "M", "غمي"), + (0xFD7B, "M", "غمى"), + (0xFD7C, "M", "فخم"), + (0xFD7E, "M", "قمح"), + (0xFD7F, "M", "قمم"), + (0xFD80, "M", "لحم"), + (0xFD81, "M", "لحي"), + (0xFD82, "M", "لحى"), + (0xFD83, "M", "لجج"), + (0xFD85, "M", "لخم"), + (0xFD87, "M", "لمح"), + (0xFD89, "M", "محج"), + (0xFD8A, "M", "محم"), + (0xFD8B, "M", "محي"), + (0xFD8C, "M", "مجح"), + (0xFD8D, "M", "مجم"), + (0xFD8E, "M", "مخج"), + (0xFD8F, "M", "مخم"), + (0xFD90, "X"), + (0xFD92, "M", "مجخ"), + (0xFD93, "M", "همج"), + (0xFD94, "M", "همم"), + (0xFD95, "M", "نحم"), + (0xFD96, "M", "نحى"), + (0xFD97, "M", "نجم"), + (0xFD99, "M", "نجى"), + (0xFD9A, "M", "نمي"), + (0xFD9B, "M", "نمى"), + (0xFD9C, "M", "يمم"), + (0xFD9E, "M", "بخي"), + (0xFD9F, "M", "تجي"), + (0xFDA0, "M", "تجى"), + (0xFDA1, "M", "تخي"), + (0xFDA2, "M", "تخى"), + (0xFDA3, "M", "تمي"), + (0xFDA4, "M", "تمى"), + (0xFDA5, "M", "جمي"), + (0xFDA6, "M", "جحى"), + (0xFDA7, "M", "جمى"), + (0xFDA8, "M", "سخى"), + (0xFDA9, "M", "صحي"), + (0xFDAA, "M", "شحي"), + ] + + +def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFDAB, "M", "ضحي"), + (0xFDAC, "M", "لجي"), + (0xFDAD, "M", "لمي"), + (0xFDAE, "M", "يحي"), + (0xFDAF, "M", "يجي"), + (0xFDB0, "M", "يمي"), + (0xFDB1, "M", "ممي"), + (0xFDB2, "M", "قمي"), + (0xFDB3, "M", "نحي"), + (0xFDB4, "M", "قمح"), + (0xFDB5, "M", "لحم"), + (0xFDB6, "M", "عمي"), + (0xFDB7, "M", "كمي"), + (0xFDB8, "M", "نجح"), + (0xFDB9, "M", "مخي"), + (0xFDBA, "M", "لجم"), + (0xFDBB, "M", "كمم"), + (0xFDBC, "M", "لجم"), + (0xFDBD, "M", "نجح"), + (0xFDBE, "M", "جحي"), + (0xFDBF, "M", "حجي"), + (0xFDC0, "M", "مجي"), + (0xFDC1, "M", "فمي"), + (0xFDC2, "M", "بحي"), + (0xFDC3, "M", "كمم"), + (0xFDC4, "M", "عجم"), + (0xFDC5, "M", "صمم"), + (0xFDC6, "M", "سخي"), + (0xFDC7, "M", "نجي"), + (0xFDC8, "X"), + (0xFDCF, "V"), + (0xFDD0, "X"), + (0xFDF0, "M", "صلے"), + (0xFDF1, "M", "قلے"), + (0xFDF2, "M", "الله"), + (0xFDF3, "M", "اكبر"), + (0xFDF4, "M", "محمد"), + (0xFDF5, "M", "صلعم"), + (0xFDF6, "M", "رسول"), + (0xFDF7, "M", "عليه"), + (0xFDF8, "M", "وسلم"), + (0xFDF9, "M", "صلى"), + (0xFDFA, "3", "صلى الله عليه وسلم"), + (0xFDFB, "3", "جل جلاله"), + (0xFDFC, "M", "ریال"), + (0xFDFD, "V"), + (0xFE00, "I"), + (0xFE10, "3", ","), + (0xFE11, "M", "、"), + (0xFE12, "X"), + (0xFE13, "3", ":"), + (0xFE14, "3", ";"), + (0xFE15, "3", "!"), + (0xFE16, "3", "?"), + (0xFE17, "M", "〖"), + (0xFE18, "M", "〗"), + (0xFE19, "X"), + (0xFE20, "V"), + (0xFE30, "X"), + (0xFE31, "M", "—"), + (0xFE32, "M", "–"), + (0xFE33, "3", "_"), + (0xFE35, "3", "("), + (0xFE36, "3", ")"), + (0xFE37, "3", "{"), + (0xFE38, "3", "}"), + (0xFE39, "M", "〔"), + (0xFE3A, "M", "〕"), + (0xFE3B, "M", "【"), + (0xFE3C, "M", "】"), + (0xFE3D, "M", "《"), + (0xFE3E, "M", "》"), + (0xFE3F, "M", "〈"), + (0xFE40, "M", "〉"), + (0xFE41, "M", "「"), + (0xFE42, "M", "」"), + (0xFE43, "M", "『"), + (0xFE44, "M", "』"), + (0xFE45, "V"), + (0xFE47, "3", "["), + (0xFE48, "3", "]"), + (0xFE49, "3", " ̅"), + (0xFE4D, "3", "_"), + (0xFE50, "3", ","), + (0xFE51, "M", "、"), + (0xFE52, "X"), + (0xFE54, "3", ";"), + (0xFE55, "3", ":"), + (0xFE56, "3", "?"), + (0xFE57, "3", "!"), + (0xFE58, "M", "—"), + (0xFE59, "3", "("), + (0xFE5A, "3", ")"), + (0xFE5B, "3", "{"), + (0xFE5C, "3", "}"), + (0xFE5D, "M", "〔"), + (0xFE5E, "M", "〕"), + (0xFE5F, "3", "#"), + (0xFE60, "3", "&"), + (0xFE61, "3", "*"), + ] + + +def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFE62, "3", "+"), + (0xFE63, "M", "-"), + (0xFE64, "3", "<"), + (0xFE65, "3", ">"), + (0xFE66, "3", "="), + (0xFE67, "X"), + (0xFE68, "3", "\\"), + (0xFE69, "3", "$"), + (0xFE6A, "3", "%"), + (0xFE6B, "3", "@"), + (0xFE6C, "X"), + (0xFE70, "3", " ً"), + (0xFE71, "M", "ـً"), + (0xFE72, "3", " ٌ"), + (0xFE73, "V"), + (0xFE74, "3", " ٍ"), + (0xFE75, "X"), + (0xFE76, "3", " َ"), + (0xFE77, "M", "ـَ"), + (0xFE78, "3", " ُ"), + (0xFE79, "M", "ـُ"), + (0xFE7A, "3", " ِ"), + (0xFE7B, "M", "ـِ"), + (0xFE7C, "3", " ّ"), + (0xFE7D, "M", "ـّ"), + (0xFE7E, "3", " ْ"), + (0xFE7F, "M", "ـْ"), + (0xFE80, "M", "ء"), + (0xFE81, "M", "آ"), + (0xFE83, "M", "أ"), + (0xFE85, "M", "ؤ"), + (0xFE87, "M", "إ"), + (0xFE89, "M", "ئ"), + (0xFE8D, "M", "ا"), + (0xFE8F, "M", "ب"), + (0xFE93, "M", "ة"), + (0xFE95, "M", "ت"), + (0xFE99, "M", "ث"), + (0xFE9D, "M", "ج"), + (0xFEA1, "M", "ح"), + (0xFEA5, "M", "خ"), + (0xFEA9, "M", "د"), + (0xFEAB, "M", "ذ"), + (0xFEAD, "M", "ر"), + (0xFEAF, "M", "ز"), + (0xFEB1, "M", "س"), + (0xFEB5, "M", "ش"), + (0xFEB9, "M", "ص"), + (0xFEBD, "M", "ض"), + (0xFEC1, "M", "ط"), + (0xFEC5, "M", "ظ"), + (0xFEC9, "M", "ع"), + (0xFECD, "M", "غ"), + (0xFED1, "M", "ف"), + (0xFED5, "M", "ق"), + (0xFED9, "M", "ك"), + (0xFEDD, "M", "ل"), + (0xFEE1, "M", "م"), + (0xFEE5, "M", "ن"), + (0xFEE9, "M", "ه"), + (0xFEED, "M", "و"), + (0xFEEF, "M", "ى"), + (0xFEF1, "M", "ي"), + (0xFEF5, "M", "لآ"), + (0xFEF7, "M", "لأ"), + (0xFEF9, "M", "لإ"), + (0xFEFB, "M", "لا"), + (0xFEFD, "X"), + (0xFEFF, "I"), + (0xFF00, "X"), + (0xFF01, "3", "!"), + (0xFF02, "3", '"'), + (0xFF03, "3", "#"), + (0xFF04, "3", "$"), + (0xFF05, "3", "%"), + (0xFF06, "3", "&"), + (0xFF07, "3", "'"), + (0xFF08, "3", "("), + (0xFF09, "3", ")"), + (0xFF0A, "3", "*"), + (0xFF0B, "3", "+"), + (0xFF0C, "3", ","), + (0xFF0D, "M", "-"), + (0xFF0E, "M", "."), + (0xFF0F, "3", "/"), + (0xFF10, "M", "0"), + (0xFF11, "M", "1"), + (0xFF12, "M", "2"), + (0xFF13, "M", "3"), + (0xFF14, "M", "4"), + (0xFF15, "M", "5"), + (0xFF16, "M", "6"), + (0xFF17, "M", "7"), + (0xFF18, "M", "8"), + (0xFF19, "M", "9"), + (0xFF1A, "3", ":"), + (0xFF1B, "3", ";"), + (0xFF1C, "3", "<"), + (0xFF1D, "3", "="), + (0xFF1E, "3", ">"), + ] + + +def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFF1F, "3", "?"), + (0xFF20, "3", "@"), + (0xFF21, "M", "a"), + (0xFF22, "M", "b"), + (0xFF23, "M", "c"), + (0xFF24, "M", "d"), + (0xFF25, "M", "e"), + (0xFF26, "M", "f"), + (0xFF27, "M", "g"), + (0xFF28, "M", "h"), + (0xFF29, "M", "i"), + (0xFF2A, "M", "j"), + (0xFF2B, "M", "k"), + (0xFF2C, "M", "l"), + (0xFF2D, "M", "m"), + (0xFF2E, "M", "n"), + (0xFF2F, "M", "o"), + (0xFF30, "M", "p"), + (0xFF31, "M", "q"), + (0xFF32, "M", "r"), + (0xFF33, "M", "s"), + (0xFF34, "M", "t"), + (0xFF35, "M", "u"), + (0xFF36, "M", "v"), + (0xFF37, "M", "w"), + (0xFF38, "M", "x"), + (0xFF39, "M", "y"), + (0xFF3A, "M", "z"), + (0xFF3B, "3", "["), + (0xFF3C, "3", "\\"), + (0xFF3D, "3", "]"), + (0xFF3E, "3", "^"), + (0xFF3F, "3", "_"), + (0xFF40, "3", "`"), + (0xFF41, "M", "a"), + (0xFF42, "M", "b"), + (0xFF43, "M", "c"), + (0xFF44, "M", "d"), + (0xFF45, "M", "e"), + (0xFF46, "M", "f"), + (0xFF47, "M", "g"), + (0xFF48, "M", "h"), + (0xFF49, "M", "i"), + (0xFF4A, "M", "j"), + (0xFF4B, "M", "k"), + (0xFF4C, "M", "l"), + (0xFF4D, "M", "m"), + (0xFF4E, "M", "n"), + (0xFF4F, "M", "o"), + (0xFF50, "M", "p"), + (0xFF51, "M", "q"), + (0xFF52, "M", "r"), + (0xFF53, "M", "s"), + (0xFF54, "M", "t"), + (0xFF55, "M", "u"), + (0xFF56, "M", "v"), + (0xFF57, "M", "w"), + (0xFF58, "M", "x"), + (0xFF59, "M", "y"), + (0xFF5A, "M", "z"), + (0xFF5B, "3", "{"), + (0xFF5C, "3", "|"), + (0xFF5D, "3", "}"), + (0xFF5E, "3", "~"), + (0xFF5F, "M", "⦅"), + (0xFF60, "M", "⦆"), + (0xFF61, "M", "."), + (0xFF62, "M", "「"), + (0xFF63, "M", "」"), + (0xFF64, "M", "、"), + (0xFF65, "M", "・"), + (0xFF66, "M", "ヲ"), + (0xFF67, "M", "ァ"), + (0xFF68, "M", "ィ"), + (0xFF69, "M", "ゥ"), + (0xFF6A, "M", "ェ"), + (0xFF6B, "M", "ォ"), + (0xFF6C, "M", "ャ"), + (0xFF6D, "M", "ュ"), + (0xFF6E, "M", "ョ"), + (0xFF6F, "M", "ッ"), + (0xFF70, "M", "ー"), + (0xFF71, "M", "ア"), + (0xFF72, "M", "イ"), + (0xFF73, "M", "ウ"), + (0xFF74, "M", "エ"), + (0xFF75, "M", "オ"), + (0xFF76, "M", "カ"), + (0xFF77, "M", "キ"), + (0xFF78, "M", "ク"), + (0xFF79, "M", "ケ"), + (0xFF7A, "M", "コ"), + (0xFF7B, "M", "サ"), + (0xFF7C, "M", "シ"), + (0xFF7D, "M", "ス"), + (0xFF7E, "M", "セ"), + (0xFF7F, "M", "ソ"), + (0xFF80, "M", "タ"), + (0xFF81, "M", "チ"), + (0xFF82, "M", "ツ"), + ] + + +def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFF83, "M", "テ"), + (0xFF84, "M", "ト"), + (0xFF85, "M", "ナ"), + (0xFF86, "M", "ニ"), + (0xFF87, "M", "ヌ"), + (0xFF88, "M", "ネ"), + (0xFF89, "M", "ノ"), + (0xFF8A, "M", "ハ"), + (0xFF8B, "M", "ヒ"), + (0xFF8C, "M", "フ"), + (0xFF8D, "M", "ヘ"), + (0xFF8E, "M", "ホ"), + (0xFF8F, "M", "マ"), + (0xFF90, "M", "ミ"), + (0xFF91, "M", "ム"), + (0xFF92, "M", "メ"), + (0xFF93, "M", "モ"), + (0xFF94, "M", "ヤ"), + (0xFF95, "M", "ユ"), + (0xFF96, "M", "ヨ"), + (0xFF97, "M", "ラ"), + (0xFF98, "M", "リ"), + (0xFF99, "M", "ル"), + (0xFF9A, "M", "レ"), + (0xFF9B, "M", "ロ"), + (0xFF9C, "M", "ワ"), + (0xFF9D, "M", "ン"), + (0xFF9E, "M", "゙"), + (0xFF9F, "M", "゚"), + (0xFFA0, "X"), + (0xFFA1, "M", "ᄀ"), + (0xFFA2, "M", "ᄁ"), + (0xFFA3, "M", "ᆪ"), + (0xFFA4, "M", "ᄂ"), + (0xFFA5, "M", "ᆬ"), + (0xFFA6, "M", "ᆭ"), + (0xFFA7, "M", "ᄃ"), + (0xFFA8, "M", "ᄄ"), + (0xFFA9, "M", "ᄅ"), + (0xFFAA, "M", "ᆰ"), + (0xFFAB, "M", "ᆱ"), + (0xFFAC, "M", "ᆲ"), + (0xFFAD, "M", "ᆳ"), + (0xFFAE, "M", "ᆴ"), + (0xFFAF, "M", "ᆵ"), + (0xFFB0, "M", "ᄚ"), + (0xFFB1, "M", "ᄆ"), + (0xFFB2, "M", "ᄇ"), + (0xFFB3, "M", "ᄈ"), + (0xFFB4, "M", "ᄡ"), + (0xFFB5, "M", "ᄉ"), + (0xFFB6, "M", "ᄊ"), + (0xFFB7, "M", "ᄋ"), + (0xFFB8, "M", "ᄌ"), + (0xFFB9, "M", "ᄍ"), + (0xFFBA, "M", "ᄎ"), + (0xFFBB, "M", "ᄏ"), + (0xFFBC, "M", "ᄐ"), + (0xFFBD, "M", "ᄑ"), + (0xFFBE, "M", "ᄒ"), + (0xFFBF, "X"), + (0xFFC2, "M", "ᅡ"), + (0xFFC3, "M", "ᅢ"), + (0xFFC4, "M", "ᅣ"), + (0xFFC5, "M", "ᅤ"), + (0xFFC6, "M", "ᅥ"), + (0xFFC7, "M", "ᅦ"), + (0xFFC8, "X"), + (0xFFCA, "M", "ᅧ"), + (0xFFCB, "M", "ᅨ"), + (0xFFCC, "M", "ᅩ"), + (0xFFCD, "M", "ᅪ"), + (0xFFCE, "M", "ᅫ"), + (0xFFCF, "M", "ᅬ"), + (0xFFD0, "X"), + (0xFFD2, "M", "ᅭ"), + (0xFFD3, "M", "ᅮ"), + (0xFFD4, "M", "ᅯ"), + (0xFFD5, "M", "ᅰ"), + (0xFFD6, "M", "ᅱ"), + (0xFFD7, "M", "ᅲ"), + (0xFFD8, "X"), + (0xFFDA, "M", "ᅳ"), + (0xFFDB, "M", "ᅴ"), + (0xFFDC, "M", "ᅵ"), + (0xFFDD, "X"), + (0xFFE0, "M", "¢"), + (0xFFE1, "M", "£"), + (0xFFE2, "M", "¬"), + (0xFFE3, "3", " ̄"), + (0xFFE4, "M", "¦"), + (0xFFE5, "M", "¥"), + (0xFFE6, "M", "₩"), + (0xFFE7, "X"), + (0xFFE8, "M", "│"), + (0xFFE9, "M", "←"), + (0xFFEA, "M", "↑"), + (0xFFEB, "M", "→"), + (0xFFEC, "M", "↓"), + (0xFFED, "M", "■"), + ] + + +def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0xFFEE, "M", "○"), + (0xFFEF, "X"), + (0x10000, "V"), + (0x1000C, "X"), + (0x1000D, "V"), + (0x10027, "X"), + (0x10028, "V"), + (0x1003B, "X"), + (0x1003C, "V"), + (0x1003E, "X"), + (0x1003F, "V"), + (0x1004E, "X"), + (0x10050, "V"), + (0x1005E, "X"), + (0x10080, "V"), + (0x100FB, "X"), + (0x10100, "V"), + (0x10103, "X"), + (0x10107, "V"), + (0x10134, "X"), + (0x10137, "V"), + (0x1018F, "X"), + (0x10190, "V"), + (0x1019D, "X"), + (0x101A0, "V"), + (0x101A1, "X"), + (0x101D0, "V"), + (0x101FE, "X"), + (0x10280, "V"), + (0x1029D, "X"), + (0x102A0, "V"), + (0x102D1, "X"), + (0x102E0, "V"), + (0x102FC, "X"), + (0x10300, "V"), + (0x10324, "X"), + (0x1032D, "V"), + (0x1034B, "X"), + (0x10350, "V"), + (0x1037B, "X"), + (0x10380, "V"), + (0x1039E, "X"), + (0x1039F, "V"), + (0x103C4, "X"), + (0x103C8, "V"), + (0x103D6, "X"), + (0x10400, "M", "𐐨"), + (0x10401, "M", "𐐩"), + (0x10402, "M", "𐐪"), + (0x10403, "M", "𐐫"), + (0x10404, "M", "𐐬"), + (0x10405, "M", "𐐭"), + (0x10406, "M", "𐐮"), + (0x10407, "M", "𐐯"), + (0x10408, "M", "𐐰"), + (0x10409, "M", "𐐱"), + (0x1040A, "M", "𐐲"), + (0x1040B, "M", "𐐳"), + (0x1040C, "M", "𐐴"), + (0x1040D, "M", "𐐵"), + (0x1040E, "M", "𐐶"), + (0x1040F, "M", "𐐷"), + (0x10410, "M", "𐐸"), + (0x10411, "M", "𐐹"), + (0x10412, "M", "𐐺"), + (0x10413, "M", "𐐻"), + (0x10414, "M", "𐐼"), + (0x10415, "M", "𐐽"), + (0x10416, "M", "𐐾"), + (0x10417, "M", "𐐿"), + (0x10418, "M", "𐑀"), + (0x10419, "M", "𐑁"), + (0x1041A, "M", "𐑂"), + (0x1041B, "M", "𐑃"), + (0x1041C, "M", "𐑄"), + (0x1041D, "M", "𐑅"), + (0x1041E, "M", "𐑆"), + (0x1041F, "M", "𐑇"), + (0x10420, "M", "𐑈"), + (0x10421, "M", "𐑉"), + (0x10422, "M", "𐑊"), + (0x10423, "M", "𐑋"), + (0x10424, "M", "𐑌"), + (0x10425, "M", "𐑍"), + (0x10426, "M", "𐑎"), + (0x10427, "M", "𐑏"), + (0x10428, "V"), + (0x1049E, "X"), + (0x104A0, "V"), + (0x104AA, "X"), + (0x104B0, "M", "𐓘"), + (0x104B1, "M", "𐓙"), + (0x104B2, "M", "𐓚"), + (0x104B3, "M", "𐓛"), + (0x104B4, "M", "𐓜"), + (0x104B5, "M", "𐓝"), + (0x104B6, "M", "𐓞"), + (0x104B7, "M", "𐓟"), + (0x104B8, "M", "𐓠"), + (0x104B9, "M", "𐓡"), + ] + + +def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x104BA, "M", "𐓢"), + (0x104BB, "M", "𐓣"), + (0x104BC, "M", "𐓤"), + (0x104BD, "M", "𐓥"), + (0x104BE, "M", "𐓦"), + (0x104BF, "M", "𐓧"), + (0x104C0, "M", "𐓨"), + (0x104C1, "M", "𐓩"), + (0x104C2, "M", "𐓪"), + (0x104C3, "M", "𐓫"), + (0x104C4, "M", "𐓬"), + (0x104C5, "M", "𐓭"), + (0x104C6, "M", "𐓮"), + (0x104C7, "M", "𐓯"), + (0x104C8, "M", "𐓰"), + (0x104C9, "M", "𐓱"), + (0x104CA, "M", "𐓲"), + (0x104CB, "M", "𐓳"), + (0x104CC, "M", "𐓴"), + (0x104CD, "M", "𐓵"), + (0x104CE, "M", "𐓶"), + (0x104CF, "M", "𐓷"), + (0x104D0, "M", "𐓸"), + (0x104D1, "M", "𐓹"), + (0x104D2, "M", "𐓺"), + (0x104D3, "M", "𐓻"), + (0x104D4, "X"), + (0x104D8, "V"), + (0x104FC, "X"), + (0x10500, "V"), + (0x10528, "X"), + (0x10530, "V"), + (0x10564, "X"), + (0x1056F, "V"), + (0x10570, "M", "𐖗"), + (0x10571, "M", "𐖘"), + (0x10572, "M", "𐖙"), + (0x10573, "M", "𐖚"), + (0x10574, "M", "𐖛"), + (0x10575, "M", "𐖜"), + (0x10576, "M", "𐖝"), + (0x10577, "M", "𐖞"), + (0x10578, "M", "𐖟"), + (0x10579, "M", "𐖠"), + (0x1057A, "M", "𐖡"), + (0x1057B, "X"), + (0x1057C, "M", "𐖣"), + (0x1057D, "M", "𐖤"), + (0x1057E, "M", "𐖥"), + (0x1057F, "M", "𐖦"), + (0x10580, "M", "𐖧"), + (0x10581, "M", "𐖨"), + (0x10582, "M", "𐖩"), + (0x10583, "M", "𐖪"), + (0x10584, "M", "𐖫"), + (0x10585, "M", "𐖬"), + (0x10586, "M", "𐖭"), + (0x10587, "M", "𐖮"), + (0x10588, "M", "𐖯"), + (0x10589, "M", "𐖰"), + (0x1058A, "M", "𐖱"), + (0x1058B, "X"), + (0x1058C, "M", "𐖳"), + (0x1058D, "M", "𐖴"), + (0x1058E, "M", "𐖵"), + (0x1058F, "M", "𐖶"), + (0x10590, "M", "𐖷"), + (0x10591, "M", "𐖸"), + (0x10592, "M", "𐖹"), + (0x10593, "X"), + (0x10594, "M", "𐖻"), + (0x10595, "M", "𐖼"), + (0x10596, "X"), + (0x10597, "V"), + (0x105A2, "X"), + (0x105A3, "V"), + (0x105B2, "X"), + (0x105B3, "V"), + (0x105BA, "X"), + (0x105BB, "V"), + (0x105BD, "X"), + (0x10600, "V"), + (0x10737, "X"), + (0x10740, "V"), + (0x10756, "X"), + (0x10760, "V"), + (0x10768, "X"), + (0x10780, "V"), + (0x10781, "M", "ː"), + (0x10782, "M", "ˑ"), + (0x10783, "M", "æ"), + (0x10784, "M", "ʙ"), + (0x10785, "M", "ɓ"), + (0x10786, "X"), + (0x10787, "M", "ʣ"), + (0x10788, "M", "ꭦ"), + (0x10789, "M", "ʥ"), + (0x1078A, "M", "ʤ"), + (0x1078B, "M", "ɖ"), + (0x1078C, "M", "ɗ"), + ] + + +def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1078D, "M", "ᶑ"), + (0x1078E, "M", "ɘ"), + (0x1078F, "M", "ɞ"), + (0x10790, "M", "ʩ"), + (0x10791, "M", "ɤ"), + (0x10792, "M", "ɢ"), + (0x10793, "M", "ɠ"), + (0x10794, "M", "ʛ"), + (0x10795, "M", "ħ"), + (0x10796, "M", "ʜ"), + (0x10797, "M", "ɧ"), + (0x10798, "M", "ʄ"), + (0x10799, "M", "ʪ"), + (0x1079A, "M", "ʫ"), + (0x1079B, "M", "ɬ"), + (0x1079C, "M", "𝼄"), + (0x1079D, "M", "ꞎ"), + (0x1079E, "M", "ɮ"), + (0x1079F, "M", "𝼅"), + (0x107A0, "M", "ʎ"), + (0x107A1, "M", "𝼆"), + (0x107A2, "M", "ø"), + (0x107A3, "M", "ɶ"), + (0x107A4, "M", "ɷ"), + (0x107A5, "M", "q"), + (0x107A6, "M", "ɺ"), + (0x107A7, "M", "𝼈"), + (0x107A8, "M", "ɽ"), + (0x107A9, "M", "ɾ"), + (0x107AA, "M", "ʀ"), + (0x107AB, "M", "ʨ"), + (0x107AC, "M", "ʦ"), + (0x107AD, "M", "ꭧ"), + (0x107AE, "M", "ʧ"), + (0x107AF, "M", "ʈ"), + (0x107B0, "M", "ⱱ"), + (0x107B1, "X"), + (0x107B2, "M", "ʏ"), + (0x107B3, "M", "ʡ"), + (0x107B4, "M", "ʢ"), + (0x107B5, "M", "ʘ"), + (0x107B6, "M", "ǀ"), + (0x107B7, "M", "ǁ"), + (0x107B8, "M", "ǂ"), + (0x107B9, "M", "𝼊"), + (0x107BA, "M", "𝼞"), + (0x107BB, "X"), + (0x10800, "V"), + (0x10806, "X"), + (0x10808, "V"), + (0x10809, "X"), + (0x1080A, "V"), + (0x10836, "X"), + (0x10837, "V"), + (0x10839, "X"), + (0x1083C, "V"), + (0x1083D, "X"), + (0x1083F, "V"), + (0x10856, "X"), + (0x10857, "V"), + (0x1089F, "X"), + (0x108A7, "V"), + (0x108B0, "X"), + (0x108E0, "V"), + (0x108F3, "X"), + (0x108F4, "V"), + (0x108F6, "X"), + (0x108FB, "V"), + (0x1091C, "X"), + (0x1091F, "V"), + (0x1093A, "X"), + (0x1093F, "V"), + (0x10940, "X"), + (0x10980, "V"), + (0x109B8, "X"), + (0x109BC, "V"), + (0x109D0, "X"), + (0x109D2, "V"), + (0x10A04, "X"), + (0x10A05, "V"), + (0x10A07, "X"), + (0x10A0C, "V"), + (0x10A14, "X"), + (0x10A15, "V"), + (0x10A18, "X"), + (0x10A19, "V"), + (0x10A36, "X"), + (0x10A38, "V"), + (0x10A3B, "X"), + (0x10A3F, "V"), + (0x10A49, "X"), + (0x10A50, "V"), + (0x10A59, "X"), + (0x10A60, "V"), + (0x10AA0, "X"), + (0x10AC0, "V"), + (0x10AE7, "X"), + (0x10AEB, "V"), + (0x10AF7, "X"), + (0x10B00, "V"), + ] + + +def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x10B36, "X"), + (0x10B39, "V"), + (0x10B56, "X"), + (0x10B58, "V"), + (0x10B73, "X"), + (0x10B78, "V"), + (0x10B92, "X"), + (0x10B99, "V"), + (0x10B9D, "X"), + (0x10BA9, "V"), + (0x10BB0, "X"), + (0x10C00, "V"), + (0x10C49, "X"), + (0x10C80, "M", "𐳀"), + (0x10C81, "M", "𐳁"), + (0x10C82, "M", "𐳂"), + (0x10C83, "M", "𐳃"), + (0x10C84, "M", "𐳄"), + (0x10C85, "M", "𐳅"), + (0x10C86, "M", "𐳆"), + (0x10C87, "M", "𐳇"), + (0x10C88, "M", "𐳈"), + (0x10C89, "M", "𐳉"), + (0x10C8A, "M", "𐳊"), + (0x10C8B, "M", "𐳋"), + (0x10C8C, "M", "𐳌"), + (0x10C8D, "M", "𐳍"), + (0x10C8E, "M", "𐳎"), + (0x10C8F, "M", "𐳏"), + (0x10C90, "M", "𐳐"), + (0x10C91, "M", "𐳑"), + (0x10C92, "M", "𐳒"), + (0x10C93, "M", "𐳓"), + (0x10C94, "M", "𐳔"), + (0x10C95, "M", "𐳕"), + (0x10C96, "M", "𐳖"), + (0x10C97, "M", "𐳗"), + (0x10C98, "M", "𐳘"), + (0x10C99, "M", "𐳙"), + (0x10C9A, "M", "𐳚"), + (0x10C9B, "M", "𐳛"), + (0x10C9C, "M", "𐳜"), + (0x10C9D, "M", "𐳝"), + (0x10C9E, "M", "𐳞"), + (0x10C9F, "M", "𐳟"), + (0x10CA0, "M", "𐳠"), + (0x10CA1, "M", "𐳡"), + (0x10CA2, "M", "𐳢"), + (0x10CA3, "M", "𐳣"), + (0x10CA4, "M", "𐳤"), + (0x10CA5, "M", "𐳥"), + (0x10CA6, "M", "𐳦"), + (0x10CA7, "M", "𐳧"), + (0x10CA8, "M", "𐳨"), + (0x10CA9, "M", "𐳩"), + (0x10CAA, "M", "𐳪"), + (0x10CAB, "M", "𐳫"), + (0x10CAC, "M", "𐳬"), + (0x10CAD, "M", "𐳭"), + (0x10CAE, "M", "𐳮"), + (0x10CAF, "M", "𐳯"), + (0x10CB0, "M", "𐳰"), + (0x10CB1, "M", "𐳱"), + (0x10CB2, "M", "𐳲"), + (0x10CB3, "X"), + (0x10CC0, "V"), + (0x10CF3, "X"), + (0x10CFA, "V"), + (0x10D28, "X"), + (0x10D30, "V"), + (0x10D3A, "X"), + (0x10E60, "V"), + (0x10E7F, "X"), + (0x10E80, "V"), + (0x10EAA, "X"), + (0x10EAB, "V"), + (0x10EAE, "X"), + (0x10EB0, "V"), + (0x10EB2, "X"), + (0x10EFD, "V"), + (0x10F28, "X"), + (0x10F30, "V"), + (0x10F5A, "X"), + (0x10F70, "V"), + (0x10F8A, "X"), + (0x10FB0, "V"), + (0x10FCC, "X"), + (0x10FE0, "V"), + (0x10FF7, "X"), + (0x11000, "V"), + (0x1104E, "X"), + (0x11052, "V"), + (0x11076, "X"), + (0x1107F, "V"), + (0x110BD, "X"), + (0x110BE, "V"), + (0x110C3, "X"), + (0x110D0, "V"), + (0x110E9, "X"), + (0x110F0, "V"), + ] + + +def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x110FA, "X"), + (0x11100, "V"), + (0x11135, "X"), + (0x11136, "V"), + (0x11148, "X"), + (0x11150, "V"), + (0x11177, "X"), + (0x11180, "V"), + (0x111E0, "X"), + (0x111E1, "V"), + (0x111F5, "X"), + (0x11200, "V"), + (0x11212, "X"), + (0x11213, "V"), + (0x11242, "X"), + (0x11280, "V"), + (0x11287, "X"), + (0x11288, "V"), + (0x11289, "X"), + (0x1128A, "V"), + (0x1128E, "X"), + (0x1128F, "V"), + (0x1129E, "X"), + (0x1129F, "V"), + (0x112AA, "X"), + (0x112B0, "V"), + (0x112EB, "X"), + (0x112F0, "V"), + (0x112FA, "X"), + (0x11300, "V"), + (0x11304, "X"), + (0x11305, "V"), + (0x1130D, "X"), + (0x1130F, "V"), + (0x11311, "X"), + (0x11313, "V"), + (0x11329, "X"), + (0x1132A, "V"), + (0x11331, "X"), + (0x11332, "V"), + (0x11334, "X"), + (0x11335, "V"), + (0x1133A, "X"), + (0x1133B, "V"), + (0x11345, "X"), + (0x11347, "V"), + (0x11349, "X"), + (0x1134B, "V"), + (0x1134E, "X"), + (0x11350, "V"), + (0x11351, "X"), + (0x11357, "V"), + (0x11358, "X"), + (0x1135D, "V"), + (0x11364, "X"), + (0x11366, "V"), + (0x1136D, "X"), + (0x11370, "V"), + (0x11375, "X"), + (0x11400, "V"), + (0x1145C, "X"), + (0x1145D, "V"), + (0x11462, "X"), + (0x11480, "V"), + (0x114C8, "X"), + (0x114D0, "V"), + (0x114DA, "X"), + (0x11580, "V"), + (0x115B6, "X"), + (0x115B8, "V"), + (0x115DE, "X"), + (0x11600, "V"), + (0x11645, "X"), + (0x11650, "V"), + (0x1165A, "X"), + (0x11660, "V"), + (0x1166D, "X"), + (0x11680, "V"), + (0x116BA, "X"), + (0x116C0, "V"), + (0x116CA, "X"), + (0x11700, "V"), + (0x1171B, "X"), + (0x1171D, "V"), + (0x1172C, "X"), + (0x11730, "V"), + (0x11747, "X"), + (0x11800, "V"), + (0x1183C, "X"), + (0x118A0, "M", "𑣀"), + (0x118A1, "M", "𑣁"), + (0x118A2, "M", "𑣂"), + (0x118A3, "M", "𑣃"), + (0x118A4, "M", "𑣄"), + (0x118A5, "M", "𑣅"), + (0x118A6, "M", "𑣆"), + (0x118A7, "M", "𑣇"), + (0x118A8, "M", "𑣈"), + (0x118A9, "M", "𑣉"), + (0x118AA, "M", "𑣊"), + ] + + +def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x118AB, "M", "𑣋"), + (0x118AC, "M", "𑣌"), + (0x118AD, "M", "𑣍"), + (0x118AE, "M", "𑣎"), + (0x118AF, "M", "𑣏"), + (0x118B0, "M", "𑣐"), + (0x118B1, "M", "𑣑"), + (0x118B2, "M", "𑣒"), + (0x118B3, "M", "𑣓"), + (0x118B4, "M", "𑣔"), + (0x118B5, "M", "𑣕"), + (0x118B6, "M", "𑣖"), + (0x118B7, "M", "𑣗"), + (0x118B8, "M", "𑣘"), + (0x118B9, "M", "𑣙"), + (0x118BA, "M", "𑣚"), + (0x118BB, "M", "𑣛"), + (0x118BC, "M", "𑣜"), + (0x118BD, "M", "𑣝"), + (0x118BE, "M", "𑣞"), + (0x118BF, "M", "𑣟"), + (0x118C0, "V"), + (0x118F3, "X"), + (0x118FF, "V"), + (0x11907, "X"), + (0x11909, "V"), + (0x1190A, "X"), + (0x1190C, "V"), + (0x11914, "X"), + (0x11915, "V"), + (0x11917, "X"), + (0x11918, "V"), + (0x11936, "X"), + (0x11937, "V"), + (0x11939, "X"), + (0x1193B, "V"), + (0x11947, "X"), + (0x11950, "V"), + (0x1195A, "X"), + (0x119A0, "V"), + (0x119A8, "X"), + (0x119AA, "V"), + (0x119D8, "X"), + (0x119DA, "V"), + (0x119E5, "X"), + (0x11A00, "V"), + (0x11A48, "X"), + (0x11A50, "V"), + (0x11AA3, "X"), + (0x11AB0, "V"), + (0x11AF9, "X"), + (0x11B00, "V"), + (0x11B0A, "X"), + (0x11C00, "V"), + (0x11C09, "X"), + (0x11C0A, "V"), + (0x11C37, "X"), + (0x11C38, "V"), + (0x11C46, "X"), + (0x11C50, "V"), + (0x11C6D, "X"), + (0x11C70, "V"), + (0x11C90, "X"), + (0x11C92, "V"), + (0x11CA8, "X"), + (0x11CA9, "V"), + (0x11CB7, "X"), + (0x11D00, "V"), + (0x11D07, "X"), + (0x11D08, "V"), + (0x11D0A, "X"), + (0x11D0B, "V"), + (0x11D37, "X"), + (0x11D3A, "V"), + (0x11D3B, "X"), + (0x11D3C, "V"), + (0x11D3E, "X"), + (0x11D3F, "V"), + (0x11D48, "X"), + (0x11D50, "V"), + (0x11D5A, "X"), + (0x11D60, "V"), + (0x11D66, "X"), + (0x11D67, "V"), + (0x11D69, "X"), + (0x11D6A, "V"), + (0x11D8F, "X"), + (0x11D90, "V"), + (0x11D92, "X"), + (0x11D93, "V"), + (0x11D99, "X"), + (0x11DA0, "V"), + (0x11DAA, "X"), + (0x11EE0, "V"), + (0x11EF9, "X"), + (0x11F00, "V"), + (0x11F11, "X"), + (0x11F12, "V"), + (0x11F3B, "X"), + (0x11F3E, "V"), + ] + + +def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x11F5A, "X"), + (0x11FB0, "V"), + (0x11FB1, "X"), + (0x11FC0, "V"), + (0x11FF2, "X"), + (0x11FFF, "V"), + (0x1239A, "X"), + (0x12400, "V"), + (0x1246F, "X"), + (0x12470, "V"), + (0x12475, "X"), + (0x12480, "V"), + (0x12544, "X"), + (0x12F90, "V"), + (0x12FF3, "X"), + (0x13000, "V"), + (0x13430, "X"), + (0x13440, "V"), + (0x13456, "X"), + (0x14400, "V"), + (0x14647, "X"), + (0x16800, "V"), + (0x16A39, "X"), + (0x16A40, "V"), + (0x16A5F, "X"), + (0x16A60, "V"), + (0x16A6A, "X"), + (0x16A6E, "V"), + (0x16ABF, "X"), + (0x16AC0, "V"), + (0x16ACA, "X"), + (0x16AD0, "V"), + (0x16AEE, "X"), + (0x16AF0, "V"), + (0x16AF6, "X"), + (0x16B00, "V"), + (0x16B46, "X"), + (0x16B50, "V"), + (0x16B5A, "X"), + (0x16B5B, "V"), + (0x16B62, "X"), + (0x16B63, "V"), + (0x16B78, "X"), + (0x16B7D, "V"), + (0x16B90, "X"), + (0x16E40, "M", "𖹠"), + (0x16E41, "M", "𖹡"), + (0x16E42, "M", "𖹢"), + (0x16E43, "M", "𖹣"), + (0x16E44, "M", "𖹤"), + (0x16E45, "M", "𖹥"), + (0x16E46, "M", "𖹦"), + (0x16E47, "M", "𖹧"), + (0x16E48, "M", "𖹨"), + (0x16E49, "M", "𖹩"), + (0x16E4A, "M", "𖹪"), + (0x16E4B, "M", "𖹫"), + (0x16E4C, "M", "𖹬"), + (0x16E4D, "M", "𖹭"), + (0x16E4E, "M", "𖹮"), + (0x16E4F, "M", "𖹯"), + (0x16E50, "M", "𖹰"), + (0x16E51, "M", "𖹱"), + (0x16E52, "M", "𖹲"), + (0x16E53, "M", "𖹳"), + (0x16E54, "M", "𖹴"), + (0x16E55, "M", "𖹵"), + (0x16E56, "M", "𖹶"), + (0x16E57, "M", "𖹷"), + (0x16E58, "M", "𖹸"), + (0x16E59, "M", "𖹹"), + (0x16E5A, "M", "𖹺"), + (0x16E5B, "M", "𖹻"), + (0x16E5C, "M", "𖹼"), + (0x16E5D, "M", "𖹽"), + (0x16E5E, "M", "𖹾"), + (0x16E5F, "M", "𖹿"), + (0x16E60, "V"), + (0x16E9B, "X"), + (0x16F00, "V"), + (0x16F4B, "X"), + (0x16F4F, "V"), + (0x16F88, "X"), + (0x16F8F, "V"), + (0x16FA0, "X"), + (0x16FE0, "V"), + (0x16FE5, "X"), + (0x16FF0, "V"), + (0x16FF2, "X"), + (0x17000, "V"), + (0x187F8, "X"), + (0x18800, "V"), + (0x18CD6, "X"), + (0x18D00, "V"), + (0x18D09, "X"), + (0x1AFF0, "V"), + (0x1AFF4, "X"), + (0x1AFF5, "V"), + (0x1AFFC, "X"), + (0x1AFFD, "V"), + ] + + +def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1AFFF, "X"), + (0x1B000, "V"), + (0x1B123, "X"), + (0x1B132, "V"), + (0x1B133, "X"), + (0x1B150, "V"), + (0x1B153, "X"), + (0x1B155, "V"), + (0x1B156, "X"), + (0x1B164, "V"), + (0x1B168, "X"), + (0x1B170, "V"), + (0x1B2FC, "X"), + (0x1BC00, "V"), + (0x1BC6B, "X"), + (0x1BC70, "V"), + (0x1BC7D, "X"), + (0x1BC80, "V"), + (0x1BC89, "X"), + (0x1BC90, "V"), + (0x1BC9A, "X"), + (0x1BC9C, "V"), + (0x1BCA0, "I"), + (0x1BCA4, "X"), + (0x1CF00, "V"), + (0x1CF2E, "X"), + (0x1CF30, "V"), + (0x1CF47, "X"), + (0x1CF50, "V"), + (0x1CFC4, "X"), + (0x1D000, "V"), + (0x1D0F6, "X"), + (0x1D100, "V"), + (0x1D127, "X"), + (0x1D129, "V"), + (0x1D15E, "M", "𝅗𝅥"), + (0x1D15F, "M", "𝅘𝅥"), + (0x1D160, "M", "𝅘𝅥𝅮"), + (0x1D161, "M", "𝅘𝅥𝅯"), + (0x1D162, "M", "𝅘𝅥𝅰"), + (0x1D163, "M", "𝅘𝅥𝅱"), + (0x1D164, "M", "𝅘𝅥𝅲"), + (0x1D165, "V"), + (0x1D173, "X"), + (0x1D17B, "V"), + (0x1D1BB, "M", "𝆹𝅥"), + (0x1D1BC, "M", "𝆺𝅥"), + (0x1D1BD, "M", "𝆹𝅥𝅮"), + (0x1D1BE, "M", "𝆺𝅥𝅮"), + (0x1D1BF, "M", "𝆹𝅥𝅯"), + (0x1D1C0, "M", "𝆺𝅥𝅯"), + (0x1D1C1, "V"), + (0x1D1EB, "X"), + (0x1D200, "V"), + (0x1D246, "X"), + (0x1D2C0, "V"), + (0x1D2D4, "X"), + (0x1D2E0, "V"), + (0x1D2F4, "X"), + (0x1D300, "V"), + (0x1D357, "X"), + (0x1D360, "V"), + (0x1D379, "X"), + (0x1D400, "M", "a"), + (0x1D401, "M", "b"), + (0x1D402, "M", "c"), + (0x1D403, "M", "d"), + (0x1D404, "M", "e"), + (0x1D405, "M", "f"), + (0x1D406, "M", "g"), + (0x1D407, "M", "h"), + (0x1D408, "M", "i"), + (0x1D409, "M", "j"), + (0x1D40A, "M", "k"), + (0x1D40B, "M", "l"), + (0x1D40C, "M", "m"), + (0x1D40D, "M", "n"), + (0x1D40E, "M", "o"), + (0x1D40F, "M", "p"), + (0x1D410, "M", "q"), + (0x1D411, "M", "r"), + (0x1D412, "M", "s"), + (0x1D413, "M", "t"), + (0x1D414, "M", "u"), + (0x1D415, "M", "v"), + (0x1D416, "M", "w"), + (0x1D417, "M", "x"), + (0x1D418, "M", "y"), + (0x1D419, "M", "z"), + (0x1D41A, "M", "a"), + (0x1D41B, "M", "b"), + (0x1D41C, "M", "c"), + (0x1D41D, "M", "d"), + (0x1D41E, "M", "e"), + (0x1D41F, "M", "f"), + (0x1D420, "M", "g"), + (0x1D421, "M", "h"), + (0x1D422, "M", "i"), + (0x1D423, "M", "j"), + (0x1D424, "M", "k"), + ] + + +def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D425, "M", "l"), + (0x1D426, "M", "m"), + (0x1D427, "M", "n"), + (0x1D428, "M", "o"), + (0x1D429, "M", "p"), + (0x1D42A, "M", "q"), + (0x1D42B, "M", "r"), + (0x1D42C, "M", "s"), + (0x1D42D, "M", "t"), + (0x1D42E, "M", "u"), + (0x1D42F, "M", "v"), + (0x1D430, "M", "w"), + (0x1D431, "M", "x"), + (0x1D432, "M", "y"), + (0x1D433, "M", "z"), + (0x1D434, "M", "a"), + (0x1D435, "M", "b"), + (0x1D436, "M", "c"), + (0x1D437, "M", "d"), + (0x1D438, "M", "e"), + (0x1D439, "M", "f"), + (0x1D43A, "M", "g"), + (0x1D43B, "M", "h"), + (0x1D43C, "M", "i"), + (0x1D43D, "M", "j"), + (0x1D43E, "M", "k"), + (0x1D43F, "M", "l"), + (0x1D440, "M", "m"), + (0x1D441, "M", "n"), + (0x1D442, "M", "o"), + (0x1D443, "M", "p"), + (0x1D444, "M", "q"), + (0x1D445, "M", "r"), + (0x1D446, "M", "s"), + (0x1D447, "M", "t"), + (0x1D448, "M", "u"), + (0x1D449, "M", "v"), + (0x1D44A, "M", "w"), + (0x1D44B, "M", "x"), + (0x1D44C, "M", "y"), + (0x1D44D, "M", "z"), + (0x1D44E, "M", "a"), + (0x1D44F, "M", "b"), + (0x1D450, "M", "c"), + (0x1D451, "M", "d"), + (0x1D452, "M", "e"), + (0x1D453, "M", "f"), + (0x1D454, "M", "g"), + (0x1D455, "X"), + (0x1D456, "M", "i"), + (0x1D457, "M", "j"), + (0x1D458, "M", "k"), + (0x1D459, "M", "l"), + (0x1D45A, "M", "m"), + (0x1D45B, "M", "n"), + (0x1D45C, "M", "o"), + (0x1D45D, "M", "p"), + (0x1D45E, "M", "q"), + (0x1D45F, "M", "r"), + (0x1D460, "M", "s"), + (0x1D461, "M", "t"), + (0x1D462, "M", "u"), + (0x1D463, "M", "v"), + (0x1D464, "M", "w"), + (0x1D465, "M", "x"), + (0x1D466, "M", "y"), + (0x1D467, "M", "z"), + (0x1D468, "M", "a"), + (0x1D469, "M", "b"), + (0x1D46A, "M", "c"), + (0x1D46B, "M", "d"), + (0x1D46C, "M", "e"), + (0x1D46D, "M", "f"), + (0x1D46E, "M", "g"), + (0x1D46F, "M", "h"), + (0x1D470, "M", "i"), + (0x1D471, "M", "j"), + (0x1D472, "M", "k"), + (0x1D473, "M", "l"), + (0x1D474, "M", "m"), + (0x1D475, "M", "n"), + (0x1D476, "M", "o"), + (0x1D477, "M", "p"), + (0x1D478, "M", "q"), + (0x1D479, "M", "r"), + (0x1D47A, "M", "s"), + (0x1D47B, "M", "t"), + (0x1D47C, "M", "u"), + (0x1D47D, "M", "v"), + (0x1D47E, "M", "w"), + (0x1D47F, "M", "x"), + (0x1D480, "M", "y"), + (0x1D481, "M", "z"), + (0x1D482, "M", "a"), + (0x1D483, "M", "b"), + (0x1D484, "M", "c"), + (0x1D485, "M", "d"), + (0x1D486, "M", "e"), + (0x1D487, "M", "f"), + (0x1D488, "M", "g"), + ] + + +def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D489, "M", "h"), + (0x1D48A, "M", "i"), + (0x1D48B, "M", "j"), + (0x1D48C, "M", "k"), + (0x1D48D, "M", "l"), + (0x1D48E, "M", "m"), + (0x1D48F, "M", "n"), + (0x1D490, "M", "o"), + (0x1D491, "M", "p"), + (0x1D492, "M", "q"), + (0x1D493, "M", "r"), + (0x1D494, "M", "s"), + (0x1D495, "M", "t"), + (0x1D496, "M", "u"), + (0x1D497, "M", "v"), + (0x1D498, "M", "w"), + (0x1D499, "M", "x"), + (0x1D49A, "M", "y"), + (0x1D49B, "M", "z"), + (0x1D49C, "M", "a"), + (0x1D49D, "X"), + (0x1D49E, "M", "c"), + (0x1D49F, "M", "d"), + (0x1D4A0, "X"), + (0x1D4A2, "M", "g"), + (0x1D4A3, "X"), + (0x1D4A5, "M", "j"), + (0x1D4A6, "M", "k"), + (0x1D4A7, "X"), + (0x1D4A9, "M", "n"), + (0x1D4AA, "M", "o"), + (0x1D4AB, "M", "p"), + (0x1D4AC, "M", "q"), + (0x1D4AD, "X"), + (0x1D4AE, "M", "s"), + (0x1D4AF, "M", "t"), + (0x1D4B0, "M", "u"), + (0x1D4B1, "M", "v"), + (0x1D4B2, "M", "w"), + (0x1D4B3, "M", "x"), + (0x1D4B4, "M", "y"), + (0x1D4B5, "M", "z"), + (0x1D4B6, "M", "a"), + (0x1D4B7, "M", "b"), + (0x1D4B8, "M", "c"), + (0x1D4B9, "M", "d"), + (0x1D4BA, "X"), + (0x1D4BB, "M", "f"), + (0x1D4BC, "X"), + (0x1D4BD, "M", "h"), + (0x1D4BE, "M", "i"), + (0x1D4BF, "M", "j"), + (0x1D4C0, "M", "k"), + (0x1D4C1, "M", "l"), + (0x1D4C2, "M", "m"), + (0x1D4C3, "M", "n"), + (0x1D4C4, "X"), + (0x1D4C5, "M", "p"), + (0x1D4C6, "M", "q"), + (0x1D4C7, "M", "r"), + (0x1D4C8, "M", "s"), + (0x1D4C9, "M", "t"), + (0x1D4CA, "M", "u"), + (0x1D4CB, "M", "v"), + (0x1D4CC, "M", "w"), + (0x1D4CD, "M", "x"), + (0x1D4CE, "M", "y"), + (0x1D4CF, "M", "z"), + (0x1D4D0, "M", "a"), + (0x1D4D1, "M", "b"), + (0x1D4D2, "M", "c"), + (0x1D4D3, "M", "d"), + (0x1D4D4, "M", "e"), + (0x1D4D5, "M", "f"), + (0x1D4D6, "M", "g"), + (0x1D4D7, "M", "h"), + (0x1D4D8, "M", "i"), + (0x1D4D9, "M", "j"), + (0x1D4DA, "M", "k"), + (0x1D4DB, "M", "l"), + (0x1D4DC, "M", "m"), + (0x1D4DD, "M", "n"), + (0x1D4DE, "M", "o"), + (0x1D4DF, "M", "p"), + (0x1D4E0, "M", "q"), + (0x1D4E1, "M", "r"), + (0x1D4E2, "M", "s"), + (0x1D4E3, "M", "t"), + (0x1D4E4, "M", "u"), + (0x1D4E5, "M", "v"), + (0x1D4E6, "M", "w"), + (0x1D4E7, "M", "x"), + (0x1D4E8, "M", "y"), + (0x1D4E9, "M", "z"), + (0x1D4EA, "M", "a"), + (0x1D4EB, "M", "b"), + (0x1D4EC, "M", "c"), + (0x1D4ED, "M", "d"), + (0x1D4EE, "M", "e"), + (0x1D4EF, "M", "f"), + ] + + +def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D4F0, "M", "g"), + (0x1D4F1, "M", "h"), + (0x1D4F2, "M", "i"), + (0x1D4F3, "M", "j"), + (0x1D4F4, "M", "k"), + (0x1D4F5, "M", "l"), + (0x1D4F6, "M", "m"), + (0x1D4F7, "M", "n"), + (0x1D4F8, "M", "o"), + (0x1D4F9, "M", "p"), + (0x1D4FA, "M", "q"), + (0x1D4FB, "M", "r"), + (0x1D4FC, "M", "s"), + (0x1D4FD, "M", "t"), + (0x1D4FE, "M", "u"), + (0x1D4FF, "M", "v"), + (0x1D500, "M", "w"), + (0x1D501, "M", "x"), + (0x1D502, "M", "y"), + (0x1D503, "M", "z"), + (0x1D504, "M", "a"), + (0x1D505, "M", "b"), + (0x1D506, "X"), + (0x1D507, "M", "d"), + (0x1D508, "M", "e"), + (0x1D509, "M", "f"), + (0x1D50A, "M", "g"), + (0x1D50B, "X"), + (0x1D50D, "M", "j"), + (0x1D50E, "M", "k"), + (0x1D50F, "M", "l"), + (0x1D510, "M", "m"), + (0x1D511, "M", "n"), + (0x1D512, "M", "o"), + (0x1D513, "M", "p"), + (0x1D514, "M", "q"), + (0x1D515, "X"), + (0x1D516, "M", "s"), + (0x1D517, "M", "t"), + (0x1D518, "M", "u"), + (0x1D519, "M", "v"), + (0x1D51A, "M", "w"), + (0x1D51B, "M", "x"), + (0x1D51C, "M", "y"), + (0x1D51D, "X"), + (0x1D51E, "M", "a"), + (0x1D51F, "M", "b"), + (0x1D520, "M", "c"), + (0x1D521, "M", "d"), + (0x1D522, "M", "e"), + (0x1D523, "M", "f"), + (0x1D524, "M", "g"), + (0x1D525, "M", "h"), + (0x1D526, "M", "i"), + (0x1D527, "M", "j"), + (0x1D528, "M", "k"), + (0x1D529, "M", "l"), + (0x1D52A, "M", "m"), + (0x1D52B, "M", "n"), + (0x1D52C, "M", "o"), + (0x1D52D, "M", "p"), + (0x1D52E, "M", "q"), + (0x1D52F, "M", "r"), + (0x1D530, "M", "s"), + (0x1D531, "M", "t"), + (0x1D532, "M", "u"), + (0x1D533, "M", "v"), + (0x1D534, "M", "w"), + (0x1D535, "M", "x"), + (0x1D536, "M", "y"), + (0x1D537, "M", "z"), + (0x1D538, "M", "a"), + (0x1D539, "M", "b"), + (0x1D53A, "X"), + (0x1D53B, "M", "d"), + (0x1D53C, "M", "e"), + (0x1D53D, "M", "f"), + (0x1D53E, "M", "g"), + (0x1D53F, "X"), + (0x1D540, "M", "i"), + (0x1D541, "M", "j"), + (0x1D542, "M", "k"), + (0x1D543, "M", "l"), + (0x1D544, "M", "m"), + (0x1D545, "X"), + (0x1D546, "M", "o"), + (0x1D547, "X"), + (0x1D54A, "M", "s"), + (0x1D54B, "M", "t"), + (0x1D54C, "M", "u"), + (0x1D54D, "M", "v"), + (0x1D54E, "M", "w"), + (0x1D54F, "M", "x"), + (0x1D550, "M", "y"), + (0x1D551, "X"), + (0x1D552, "M", "a"), + (0x1D553, "M", "b"), + (0x1D554, "M", "c"), + (0x1D555, "M", "d"), + (0x1D556, "M", "e"), + ] + + +def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D557, "M", "f"), + (0x1D558, "M", "g"), + (0x1D559, "M", "h"), + (0x1D55A, "M", "i"), + (0x1D55B, "M", "j"), + (0x1D55C, "M", "k"), + (0x1D55D, "M", "l"), + (0x1D55E, "M", "m"), + (0x1D55F, "M", "n"), + (0x1D560, "M", "o"), + (0x1D561, "M", "p"), + (0x1D562, "M", "q"), + (0x1D563, "M", "r"), + (0x1D564, "M", "s"), + (0x1D565, "M", "t"), + (0x1D566, "M", "u"), + (0x1D567, "M", "v"), + (0x1D568, "M", "w"), + (0x1D569, "M", "x"), + (0x1D56A, "M", "y"), + (0x1D56B, "M", "z"), + (0x1D56C, "M", "a"), + (0x1D56D, "M", "b"), + (0x1D56E, "M", "c"), + (0x1D56F, "M", "d"), + (0x1D570, "M", "e"), + (0x1D571, "M", "f"), + (0x1D572, "M", "g"), + (0x1D573, "M", "h"), + (0x1D574, "M", "i"), + (0x1D575, "M", "j"), + (0x1D576, "M", "k"), + (0x1D577, "M", "l"), + (0x1D578, "M", "m"), + (0x1D579, "M", "n"), + (0x1D57A, "M", "o"), + (0x1D57B, "M", "p"), + (0x1D57C, "M", "q"), + (0x1D57D, "M", "r"), + (0x1D57E, "M", "s"), + (0x1D57F, "M", "t"), + (0x1D580, "M", "u"), + (0x1D581, "M", "v"), + (0x1D582, "M", "w"), + (0x1D583, "M", "x"), + (0x1D584, "M", "y"), + (0x1D585, "M", "z"), + (0x1D586, "M", "a"), + (0x1D587, "M", "b"), + (0x1D588, "M", "c"), + (0x1D589, "M", "d"), + (0x1D58A, "M", "e"), + (0x1D58B, "M", "f"), + (0x1D58C, "M", "g"), + (0x1D58D, "M", "h"), + (0x1D58E, "M", "i"), + (0x1D58F, "M", "j"), + (0x1D590, "M", "k"), + (0x1D591, "M", "l"), + (0x1D592, "M", "m"), + (0x1D593, "M", "n"), + (0x1D594, "M", "o"), + (0x1D595, "M", "p"), + (0x1D596, "M", "q"), + (0x1D597, "M", "r"), + (0x1D598, "M", "s"), + (0x1D599, "M", "t"), + (0x1D59A, "M", "u"), + (0x1D59B, "M", "v"), + (0x1D59C, "M", "w"), + (0x1D59D, "M", "x"), + (0x1D59E, "M", "y"), + (0x1D59F, "M", "z"), + (0x1D5A0, "M", "a"), + (0x1D5A1, "M", "b"), + (0x1D5A2, "M", "c"), + (0x1D5A3, "M", "d"), + (0x1D5A4, "M", "e"), + (0x1D5A5, "M", "f"), + (0x1D5A6, "M", "g"), + (0x1D5A7, "M", "h"), + (0x1D5A8, "M", "i"), + (0x1D5A9, "M", "j"), + (0x1D5AA, "M", "k"), + (0x1D5AB, "M", "l"), + (0x1D5AC, "M", "m"), + (0x1D5AD, "M", "n"), + (0x1D5AE, "M", "o"), + (0x1D5AF, "M", "p"), + (0x1D5B0, "M", "q"), + (0x1D5B1, "M", "r"), + (0x1D5B2, "M", "s"), + (0x1D5B3, "M", "t"), + (0x1D5B4, "M", "u"), + (0x1D5B5, "M", "v"), + (0x1D5B6, "M", "w"), + (0x1D5B7, "M", "x"), + (0x1D5B8, "M", "y"), + (0x1D5B9, "M", "z"), + (0x1D5BA, "M", "a"), + ] + + +def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D5BB, "M", "b"), + (0x1D5BC, "M", "c"), + (0x1D5BD, "M", "d"), + (0x1D5BE, "M", "e"), + (0x1D5BF, "M", "f"), + (0x1D5C0, "M", "g"), + (0x1D5C1, "M", "h"), + (0x1D5C2, "M", "i"), + (0x1D5C3, "M", "j"), + (0x1D5C4, "M", "k"), + (0x1D5C5, "M", "l"), + (0x1D5C6, "M", "m"), + (0x1D5C7, "M", "n"), + (0x1D5C8, "M", "o"), + (0x1D5C9, "M", "p"), + (0x1D5CA, "M", "q"), + (0x1D5CB, "M", "r"), + (0x1D5CC, "M", "s"), + (0x1D5CD, "M", "t"), + (0x1D5CE, "M", "u"), + (0x1D5CF, "M", "v"), + (0x1D5D0, "M", "w"), + (0x1D5D1, "M", "x"), + (0x1D5D2, "M", "y"), + (0x1D5D3, "M", "z"), + (0x1D5D4, "M", "a"), + (0x1D5D5, "M", "b"), + (0x1D5D6, "M", "c"), + (0x1D5D7, "M", "d"), + (0x1D5D8, "M", "e"), + (0x1D5D9, "M", "f"), + (0x1D5DA, "M", "g"), + (0x1D5DB, "M", "h"), + (0x1D5DC, "M", "i"), + (0x1D5DD, "M", "j"), + (0x1D5DE, "M", "k"), + (0x1D5DF, "M", "l"), + (0x1D5E0, "M", "m"), + (0x1D5E1, "M", "n"), + (0x1D5E2, "M", "o"), + (0x1D5E3, "M", "p"), + (0x1D5E4, "M", "q"), + (0x1D5E5, "M", "r"), + (0x1D5E6, "M", "s"), + (0x1D5E7, "M", "t"), + (0x1D5E8, "M", "u"), + (0x1D5E9, "M", "v"), + (0x1D5EA, "M", "w"), + (0x1D5EB, "M", "x"), + (0x1D5EC, "M", "y"), + (0x1D5ED, "M", "z"), + (0x1D5EE, "M", "a"), + (0x1D5EF, "M", "b"), + (0x1D5F0, "M", "c"), + (0x1D5F1, "M", "d"), + (0x1D5F2, "M", "e"), + (0x1D5F3, "M", "f"), + (0x1D5F4, "M", "g"), + (0x1D5F5, "M", "h"), + (0x1D5F6, "M", "i"), + (0x1D5F7, "M", "j"), + (0x1D5F8, "M", "k"), + (0x1D5F9, "M", "l"), + (0x1D5FA, "M", "m"), + (0x1D5FB, "M", "n"), + (0x1D5FC, "M", "o"), + (0x1D5FD, "M", "p"), + (0x1D5FE, "M", "q"), + (0x1D5FF, "M", "r"), + (0x1D600, "M", "s"), + (0x1D601, "M", "t"), + (0x1D602, "M", "u"), + (0x1D603, "M", "v"), + (0x1D604, "M", "w"), + (0x1D605, "M", "x"), + (0x1D606, "M", "y"), + (0x1D607, "M", "z"), + (0x1D608, "M", "a"), + (0x1D609, "M", "b"), + (0x1D60A, "M", "c"), + (0x1D60B, "M", "d"), + (0x1D60C, "M", "e"), + (0x1D60D, "M", "f"), + (0x1D60E, "M", "g"), + (0x1D60F, "M", "h"), + (0x1D610, "M", "i"), + (0x1D611, "M", "j"), + (0x1D612, "M", "k"), + (0x1D613, "M", "l"), + (0x1D614, "M", "m"), + (0x1D615, "M", "n"), + (0x1D616, "M", "o"), + (0x1D617, "M", "p"), + (0x1D618, "M", "q"), + (0x1D619, "M", "r"), + (0x1D61A, "M", "s"), + (0x1D61B, "M", "t"), + (0x1D61C, "M", "u"), + (0x1D61D, "M", "v"), + (0x1D61E, "M", "w"), + ] + + +def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D61F, "M", "x"), + (0x1D620, "M", "y"), + (0x1D621, "M", "z"), + (0x1D622, "M", "a"), + (0x1D623, "M", "b"), + (0x1D624, "M", "c"), + (0x1D625, "M", "d"), + (0x1D626, "M", "e"), + (0x1D627, "M", "f"), + (0x1D628, "M", "g"), + (0x1D629, "M", "h"), + (0x1D62A, "M", "i"), + (0x1D62B, "M", "j"), + (0x1D62C, "M", "k"), + (0x1D62D, "M", "l"), + (0x1D62E, "M", "m"), + (0x1D62F, "M", "n"), + (0x1D630, "M", "o"), + (0x1D631, "M", "p"), + (0x1D632, "M", "q"), + (0x1D633, "M", "r"), + (0x1D634, "M", "s"), + (0x1D635, "M", "t"), + (0x1D636, "M", "u"), + (0x1D637, "M", "v"), + (0x1D638, "M", "w"), + (0x1D639, "M", "x"), + (0x1D63A, "M", "y"), + (0x1D63B, "M", "z"), + (0x1D63C, "M", "a"), + (0x1D63D, "M", "b"), + (0x1D63E, "M", "c"), + (0x1D63F, "M", "d"), + (0x1D640, "M", "e"), + (0x1D641, "M", "f"), + (0x1D642, "M", "g"), + (0x1D643, "M", "h"), + (0x1D644, "M", "i"), + (0x1D645, "M", "j"), + (0x1D646, "M", "k"), + (0x1D647, "M", "l"), + (0x1D648, "M", "m"), + (0x1D649, "M", "n"), + (0x1D64A, "M", "o"), + (0x1D64B, "M", "p"), + (0x1D64C, "M", "q"), + (0x1D64D, "M", "r"), + (0x1D64E, "M", "s"), + (0x1D64F, "M", "t"), + (0x1D650, "M", "u"), + (0x1D651, "M", "v"), + (0x1D652, "M", "w"), + (0x1D653, "M", "x"), + (0x1D654, "M", "y"), + (0x1D655, "M", "z"), + (0x1D656, "M", "a"), + (0x1D657, "M", "b"), + (0x1D658, "M", "c"), + (0x1D659, "M", "d"), + (0x1D65A, "M", "e"), + (0x1D65B, "M", "f"), + (0x1D65C, "M", "g"), + (0x1D65D, "M", "h"), + (0x1D65E, "M", "i"), + (0x1D65F, "M", "j"), + (0x1D660, "M", "k"), + (0x1D661, "M", "l"), + (0x1D662, "M", "m"), + (0x1D663, "M", "n"), + (0x1D664, "M", "o"), + (0x1D665, "M", "p"), + (0x1D666, "M", "q"), + (0x1D667, "M", "r"), + (0x1D668, "M", "s"), + (0x1D669, "M", "t"), + (0x1D66A, "M", "u"), + (0x1D66B, "M", "v"), + (0x1D66C, "M", "w"), + (0x1D66D, "M", "x"), + (0x1D66E, "M", "y"), + (0x1D66F, "M", "z"), + (0x1D670, "M", "a"), + (0x1D671, "M", "b"), + (0x1D672, "M", "c"), + (0x1D673, "M", "d"), + (0x1D674, "M", "e"), + (0x1D675, "M", "f"), + (0x1D676, "M", "g"), + (0x1D677, "M", "h"), + (0x1D678, "M", "i"), + (0x1D679, "M", "j"), + (0x1D67A, "M", "k"), + (0x1D67B, "M", "l"), + (0x1D67C, "M", "m"), + (0x1D67D, "M", "n"), + (0x1D67E, "M", "o"), + (0x1D67F, "M", "p"), + (0x1D680, "M", "q"), + (0x1D681, "M", "r"), + (0x1D682, "M", "s"), + ] + + +def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D683, "M", "t"), + (0x1D684, "M", "u"), + (0x1D685, "M", "v"), + (0x1D686, "M", "w"), + (0x1D687, "M", "x"), + (0x1D688, "M", "y"), + (0x1D689, "M", "z"), + (0x1D68A, "M", "a"), + (0x1D68B, "M", "b"), + (0x1D68C, "M", "c"), + (0x1D68D, "M", "d"), + (0x1D68E, "M", "e"), + (0x1D68F, "M", "f"), + (0x1D690, "M", "g"), + (0x1D691, "M", "h"), + (0x1D692, "M", "i"), + (0x1D693, "M", "j"), + (0x1D694, "M", "k"), + (0x1D695, "M", "l"), + (0x1D696, "M", "m"), + (0x1D697, "M", "n"), + (0x1D698, "M", "o"), + (0x1D699, "M", "p"), + (0x1D69A, "M", "q"), + (0x1D69B, "M", "r"), + (0x1D69C, "M", "s"), + (0x1D69D, "M", "t"), + (0x1D69E, "M", "u"), + (0x1D69F, "M", "v"), + (0x1D6A0, "M", "w"), + (0x1D6A1, "M", "x"), + (0x1D6A2, "M", "y"), + (0x1D6A3, "M", "z"), + (0x1D6A4, "M", "ı"), + (0x1D6A5, "M", "ȷ"), + (0x1D6A6, "X"), + (0x1D6A8, "M", "α"), + (0x1D6A9, "M", "β"), + (0x1D6AA, "M", "γ"), + (0x1D6AB, "M", "δ"), + (0x1D6AC, "M", "ε"), + (0x1D6AD, "M", "ζ"), + (0x1D6AE, "M", "η"), + (0x1D6AF, "M", "θ"), + (0x1D6B0, "M", "ι"), + (0x1D6B1, "M", "κ"), + (0x1D6B2, "M", "λ"), + (0x1D6B3, "M", "μ"), + (0x1D6B4, "M", "ν"), + (0x1D6B5, "M", "ξ"), + (0x1D6B6, "M", "ο"), + (0x1D6B7, "M", "π"), + (0x1D6B8, "M", "ρ"), + (0x1D6B9, "M", "θ"), + (0x1D6BA, "M", "σ"), + (0x1D6BB, "M", "τ"), + (0x1D6BC, "M", "υ"), + (0x1D6BD, "M", "φ"), + (0x1D6BE, "M", "χ"), + (0x1D6BF, "M", "ψ"), + (0x1D6C0, "M", "ω"), + (0x1D6C1, "M", "∇"), + (0x1D6C2, "M", "α"), + (0x1D6C3, "M", "β"), + (0x1D6C4, "M", "γ"), + (0x1D6C5, "M", "δ"), + (0x1D6C6, "M", "ε"), + (0x1D6C7, "M", "ζ"), + (0x1D6C8, "M", "η"), + (0x1D6C9, "M", "θ"), + (0x1D6CA, "M", "ι"), + (0x1D6CB, "M", "κ"), + (0x1D6CC, "M", "λ"), + (0x1D6CD, "M", "μ"), + (0x1D6CE, "M", "ν"), + (0x1D6CF, "M", "ξ"), + (0x1D6D0, "M", "ο"), + (0x1D6D1, "M", "π"), + (0x1D6D2, "M", "ρ"), + (0x1D6D3, "M", "σ"), + (0x1D6D5, "M", "τ"), + (0x1D6D6, "M", "υ"), + (0x1D6D7, "M", "φ"), + (0x1D6D8, "M", "χ"), + (0x1D6D9, "M", "ψ"), + (0x1D6DA, "M", "ω"), + (0x1D6DB, "M", "∂"), + (0x1D6DC, "M", "ε"), + (0x1D6DD, "M", "θ"), + (0x1D6DE, "M", "κ"), + (0x1D6DF, "M", "φ"), + (0x1D6E0, "M", "ρ"), + (0x1D6E1, "M", "π"), + (0x1D6E2, "M", "α"), + (0x1D6E3, "M", "β"), + (0x1D6E4, "M", "γ"), + (0x1D6E5, "M", "δ"), + (0x1D6E6, "M", "ε"), + (0x1D6E7, "M", "ζ"), + (0x1D6E8, "M", "η"), + ] + + +def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D6E9, "M", "θ"), + (0x1D6EA, "M", "ι"), + (0x1D6EB, "M", "κ"), + (0x1D6EC, "M", "λ"), + (0x1D6ED, "M", "μ"), + (0x1D6EE, "M", "ν"), + (0x1D6EF, "M", "ξ"), + (0x1D6F0, "M", "ο"), + (0x1D6F1, "M", "π"), + (0x1D6F2, "M", "ρ"), + (0x1D6F3, "M", "θ"), + (0x1D6F4, "M", "σ"), + (0x1D6F5, "M", "τ"), + (0x1D6F6, "M", "υ"), + (0x1D6F7, "M", "φ"), + (0x1D6F8, "M", "χ"), + (0x1D6F9, "M", "ψ"), + (0x1D6FA, "M", "ω"), + (0x1D6FB, "M", "∇"), + (0x1D6FC, "M", "α"), + (0x1D6FD, "M", "β"), + (0x1D6FE, "M", "γ"), + (0x1D6FF, "M", "δ"), + (0x1D700, "M", "ε"), + (0x1D701, "M", "ζ"), + (0x1D702, "M", "η"), + (0x1D703, "M", "θ"), + (0x1D704, "M", "ι"), + (0x1D705, "M", "κ"), + (0x1D706, "M", "λ"), + (0x1D707, "M", "μ"), + (0x1D708, "M", "ν"), + (0x1D709, "M", "ξ"), + (0x1D70A, "M", "ο"), + (0x1D70B, "M", "π"), + (0x1D70C, "M", "ρ"), + (0x1D70D, "M", "σ"), + (0x1D70F, "M", "τ"), + (0x1D710, "M", "υ"), + (0x1D711, "M", "φ"), + (0x1D712, "M", "χ"), + (0x1D713, "M", "ψ"), + (0x1D714, "M", "ω"), + (0x1D715, "M", "∂"), + (0x1D716, "M", "ε"), + (0x1D717, "M", "θ"), + (0x1D718, "M", "κ"), + (0x1D719, "M", "φ"), + (0x1D71A, "M", "ρ"), + (0x1D71B, "M", "π"), + (0x1D71C, "M", "α"), + (0x1D71D, "M", "β"), + (0x1D71E, "M", "γ"), + (0x1D71F, "M", "δ"), + (0x1D720, "M", "ε"), + (0x1D721, "M", "ζ"), + (0x1D722, "M", "η"), + (0x1D723, "M", "θ"), + (0x1D724, "M", "ι"), + (0x1D725, "M", "κ"), + (0x1D726, "M", "λ"), + (0x1D727, "M", "μ"), + (0x1D728, "M", "ν"), + (0x1D729, "M", "ξ"), + (0x1D72A, "M", "ο"), + (0x1D72B, "M", "π"), + (0x1D72C, "M", "ρ"), + (0x1D72D, "M", "θ"), + (0x1D72E, "M", "σ"), + (0x1D72F, "M", "τ"), + (0x1D730, "M", "υ"), + (0x1D731, "M", "φ"), + (0x1D732, "M", "χ"), + (0x1D733, "M", "ψ"), + (0x1D734, "M", "ω"), + (0x1D735, "M", "∇"), + (0x1D736, "M", "α"), + (0x1D737, "M", "β"), + (0x1D738, "M", "γ"), + (0x1D739, "M", "δ"), + (0x1D73A, "M", "ε"), + (0x1D73B, "M", "ζ"), + (0x1D73C, "M", "η"), + (0x1D73D, "M", "θ"), + (0x1D73E, "M", "ι"), + (0x1D73F, "M", "κ"), + (0x1D740, "M", "λ"), + (0x1D741, "M", "μ"), + (0x1D742, "M", "ν"), + (0x1D743, "M", "ξ"), + (0x1D744, "M", "ο"), + (0x1D745, "M", "π"), + (0x1D746, "M", "ρ"), + (0x1D747, "M", "σ"), + (0x1D749, "M", "τ"), + (0x1D74A, "M", "υ"), + (0x1D74B, "M", "φ"), + (0x1D74C, "M", "χ"), + (0x1D74D, "M", "ψ"), + (0x1D74E, "M", "ω"), + ] + + +def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D74F, "M", "∂"), + (0x1D750, "M", "ε"), + (0x1D751, "M", "θ"), + (0x1D752, "M", "κ"), + (0x1D753, "M", "φ"), + (0x1D754, "M", "ρ"), + (0x1D755, "M", "π"), + (0x1D756, "M", "α"), + (0x1D757, "M", "β"), + (0x1D758, "M", "γ"), + (0x1D759, "M", "δ"), + (0x1D75A, "M", "ε"), + (0x1D75B, "M", "ζ"), + (0x1D75C, "M", "η"), + (0x1D75D, "M", "θ"), + (0x1D75E, "M", "ι"), + (0x1D75F, "M", "κ"), + (0x1D760, "M", "λ"), + (0x1D761, "M", "μ"), + (0x1D762, "M", "ν"), + (0x1D763, "M", "ξ"), + (0x1D764, "M", "ο"), + (0x1D765, "M", "π"), + (0x1D766, "M", "ρ"), + (0x1D767, "M", "θ"), + (0x1D768, "M", "σ"), + (0x1D769, "M", "τ"), + (0x1D76A, "M", "υ"), + (0x1D76B, "M", "φ"), + (0x1D76C, "M", "χ"), + (0x1D76D, "M", "ψ"), + (0x1D76E, "M", "ω"), + (0x1D76F, "M", "∇"), + (0x1D770, "M", "α"), + (0x1D771, "M", "β"), + (0x1D772, "M", "γ"), + (0x1D773, "M", "δ"), + (0x1D774, "M", "ε"), + (0x1D775, "M", "ζ"), + (0x1D776, "M", "η"), + (0x1D777, "M", "θ"), + (0x1D778, "M", "ι"), + (0x1D779, "M", "κ"), + (0x1D77A, "M", "λ"), + (0x1D77B, "M", "μ"), + (0x1D77C, "M", "ν"), + (0x1D77D, "M", "ξ"), + (0x1D77E, "M", "ο"), + (0x1D77F, "M", "π"), + (0x1D780, "M", "ρ"), + (0x1D781, "M", "σ"), + (0x1D783, "M", "τ"), + (0x1D784, "M", "υ"), + (0x1D785, "M", "φ"), + (0x1D786, "M", "χ"), + (0x1D787, "M", "ψ"), + (0x1D788, "M", "ω"), + (0x1D789, "M", "∂"), + (0x1D78A, "M", "ε"), + (0x1D78B, "M", "θ"), + (0x1D78C, "M", "κ"), + (0x1D78D, "M", "φ"), + (0x1D78E, "M", "ρ"), + (0x1D78F, "M", "π"), + (0x1D790, "M", "α"), + (0x1D791, "M", "β"), + (0x1D792, "M", "γ"), + (0x1D793, "M", "δ"), + (0x1D794, "M", "ε"), + (0x1D795, "M", "ζ"), + (0x1D796, "M", "η"), + (0x1D797, "M", "θ"), + (0x1D798, "M", "ι"), + (0x1D799, "M", "κ"), + (0x1D79A, "M", "λ"), + (0x1D79B, "M", "μ"), + (0x1D79C, "M", "ν"), + (0x1D79D, "M", "ξ"), + (0x1D79E, "M", "ο"), + (0x1D79F, "M", "π"), + (0x1D7A0, "M", "ρ"), + (0x1D7A1, "M", "θ"), + (0x1D7A2, "M", "σ"), + (0x1D7A3, "M", "τ"), + (0x1D7A4, "M", "υ"), + (0x1D7A5, "M", "φ"), + (0x1D7A6, "M", "χ"), + (0x1D7A7, "M", "ψ"), + (0x1D7A8, "M", "ω"), + (0x1D7A9, "M", "∇"), + (0x1D7AA, "M", "α"), + (0x1D7AB, "M", "β"), + (0x1D7AC, "M", "γ"), + (0x1D7AD, "M", "δ"), + (0x1D7AE, "M", "ε"), + (0x1D7AF, "M", "ζ"), + (0x1D7B0, "M", "η"), + (0x1D7B1, "M", "θ"), + (0x1D7B2, "M", "ι"), + (0x1D7B3, "M", "κ"), + ] + + +def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1D7B4, "M", "λ"), + (0x1D7B5, "M", "μ"), + (0x1D7B6, "M", "ν"), + (0x1D7B7, "M", "ξ"), + (0x1D7B8, "M", "ο"), + (0x1D7B9, "M", "π"), + (0x1D7BA, "M", "ρ"), + (0x1D7BB, "M", "σ"), + (0x1D7BD, "M", "τ"), + (0x1D7BE, "M", "υ"), + (0x1D7BF, "M", "φ"), + (0x1D7C0, "M", "χ"), + (0x1D7C1, "M", "ψ"), + (0x1D7C2, "M", "ω"), + (0x1D7C3, "M", "∂"), + (0x1D7C4, "M", "ε"), + (0x1D7C5, "M", "θ"), + (0x1D7C6, "M", "κ"), + (0x1D7C7, "M", "φ"), + (0x1D7C8, "M", "ρ"), + (0x1D7C9, "M", "π"), + (0x1D7CA, "M", "ϝ"), + (0x1D7CC, "X"), + (0x1D7CE, "M", "0"), + (0x1D7CF, "M", "1"), + (0x1D7D0, "M", "2"), + (0x1D7D1, "M", "3"), + (0x1D7D2, "M", "4"), + (0x1D7D3, "M", "5"), + (0x1D7D4, "M", "6"), + (0x1D7D5, "M", "7"), + (0x1D7D6, "M", "8"), + (0x1D7D7, "M", "9"), + (0x1D7D8, "M", "0"), + (0x1D7D9, "M", "1"), + (0x1D7DA, "M", "2"), + (0x1D7DB, "M", "3"), + (0x1D7DC, "M", "4"), + (0x1D7DD, "M", "5"), + (0x1D7DE, "M", "6"), + (0x1D7DF, "M", "7"), + (0x1D7E0, "M", "8"), + (0x1D7E1, "M", "9"), + (0x1D7E2, "M", "0"), + (0x1D7E3, "M", "1"), + (0x1D7E4, "M", "2"), + (0x1D7E5, "M", "3"), + (0x1D7E6, "M", "4"), + (0x1D7E7, "M", "5"), + (0x1D7E8, "M", "6"), + (0x1D7E9, "M", "7"), + (0x1D7EA, "M", "8"), + (0x1D7EB, "M", "9"), + (0x1D7EC, "M", "0"), + (0x1D7ED, "M", "1"), + (0x1D7EE, "M", "2"), + (0x1D7EF, "M", "3"), + (0x1D7F0, "M", "4"), + (0x1D7F1, "M", "5"), + (0x1D7F2, "M", "6"), + (0x1D7F3, "M", "7"), + (0x1D7F4, "M", "8"), + (0x1D7F5, "M", "9"), + (0x1D7F6, "M", "0"), + (0x1D7F7, "M", "1"), + (0x1D7F8, "M", "2"), + (0x1D7F9, "M", "3"), + (0x1D7FA, "M", "4"), + (0x1D7FB, "M", "5"), + (0x1D7FC, "M", "6"), + (0x1D7FD, "M", "7"), + (0x1D7FE, "M", "8"), + (0x1D7FF, "M", "9"), + (0x1D800, "V"), + (0x1DA8C, "X"), + (0x1DA9B, "V"), + (0x1DAA0, "X"), + (0x1DAA1, "V"), + (0x1DAB0, "X"), + (0x1DF00, "V"), + (0x1DF1F, "X"), + (0x1DF25, "V"), + (0x1DF2B, "X"), + (0x1E000, "V"), + (0x1E007, "X"), + (0x1E008, "V"), + (0x1E019, "X"), + (0x1E01B, "V"), + (0x1E022, "X"), + (0x1E023, "V"), + (0x1E025, "X"), + (0x1E026, "V"), + (0x1E02B, "X"), + (0x1E030, "M", "а"), + (0x1E031, "M", "б"), + (0x1E032, "M", "в"), + (0x1E033, "M", "г"), + (0x1E034, "M", "д"), + (0x1E035, "M", "е"), + (0x1E036, "M", "ж"), + ] + + +def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E037, "M", "з"), + (0x1E038, "M", "и"), + (0x1E039, "M", "к"), + (0x1E03A, "M", "л"), + (0x1E03B, "M", "м"), + (0x1E03C, "M", "о"), + (0x1E03D, "M", "п"), + (0x1E03E, "M", "р"), + (0x1E03F, "M", "с"), + (0x1E040, "M", "т"), + (0x1E041, "M", "у"), + (0x1E042, "M", "ф"), + (0x1E043, "M", "х"), + (0x1E044, "M", "ц"), + (0x1E045, "M", "ч"), + (0x1E046, "M", "ш"), + (0x1E047, "M", "ы"), + (0x1E048, "M", "э"), + (0x1E049, "M", "ю"), + (0x1E04A, "M", "ꚉ"), + (0x1E04B, "M", "ә"), + (0x1E04C, "M", "і"), + (0x1E04D, "M", "ј"), + (0x1E04E, "M", "ө"), + (0x1E04F, "M", "ү"), + (0x1E050, "M", "ӏ"), + (0x1E051, "M", "а"), + (0x1E052, "M", "б"), + (0x1E053, "M", "в"), + (0x1E054, "M", "г"), + (0x1E055, "M", "д"), + (0x1E056, "M", "е"), + (0x1E057, "M", "ж"), + (0x1E058, "M", "з"), + (0x1E059, "M", "и"), + (0x1E05A, "M", "к"), + (0x1E05B, "M", "л"), + (0x1E05C, "M", "о"), + (0x1E05D, "M", "п"), + (0x1E05E, "M", "с"), + (0x1E05F, "M", "у"), + (0x1E060, "M", "ф"), + (0x1E061, "M", "х"), + (0x1E062, "M", "ц"), + (0x1E063, "M", "ч"), + (0x1E064, "M", "ш"), + (0x1E065, "M", "ъ"), + (0x1E066, "M", "ы"), + (0x1E067, "M", "ґ"), + (0x1E068, "M", "і"), + (0x1E069, "M", "ѕ"), + (0x1E06A, "M", "џ"), + (0x1E06B, "M", "ҫ"), + (0x1E06C, "M", "ꙑ"), + (0x1E06D, "M", "ұ"), + (0x1E06E, "X"), + (0x1E08F, "V"), + (0x1E090, "X"), + (0x1E100, "V"), + (0x1E12D, "X"), + (0x1E130, "V"), + (0x1E13E, "X"), + (0x1E140, "V"), + (0x1E14A, "X"), + (0x1E14E, "V"), + (0x1E150, "X"), + (0x1E290, "V"), + (0x1E2AF, "X"), + (0x1E2C0, "V"), + (0x1E2FA, "X"), + (0x1E2FF, "V"), + (0x1E300, "X"), + (0x1E4D0, "V"), + (0x1E4FA, "X"), + (0x1E7E0, "V"), + (0x1E7E7, "X"), + (0x1E7E8, "V"), + (0x1E7EC, "X"), + (0x1E7ED, "V"), + (0x1E7EF, "X"), + (0x1E7F0, "V"), + (0x1E7FF, "X"), + (0x1E800, "V"), + (0x1E8C5, "X"), + (0x1E8C7, "V"), + (0x1E8D7, "X"), + (0x1E900, "M", "𞤢"), + (0x1E901, "M", "𞤣"), + (0x1E902, "M", "𞤤"), + (0x1E903, "M", "𞤥"), + (0x1E904, "M", "𞤦"), + (0x1E905, "M", "𞤧"), + (0x1E906, "M", "𞤨"), + (0x1E907, "M", "𞤩"), + (0x1E908, "M", "𞤪"), + (0x1E909, "M", "𞤫"), + (0x1E90A, "M", "𞤬"), + (0x1E90B, "M", "𞤭"), + (0x1E90C, "M", "𞤮"), + (0x1E90D, "M", "𞤯"), + ] + + +def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1E90E, "M", "𞤰"), + (0x1E90F, "M", "𞤱"), + (0x1E910, "M", "𞤲"), + (0x1E911, "M", "𞤳"), + (0x1E912, "M", "𞤴"), + (0x1E913, "M", "𞤵"), + (0x1E914, "M", "𞤶"), + (0x1E915, "M", "𞤷"), + (0x1E916, "M", "𞤸"), + (0x1E917, "M", "𞤹"), + (0x1E918, "M", "𞤺"), + (0x1E919, "M", "𞤻"), + (0x1E91A, "M", "𞤼"), + (0x1E91B, "M", "𞤽"), + (0x1E91C, "M", "𞤾"), + (0x1E91D, "M", "𞤿"), + (0x1E91E, "M", "𞥀"), + (0x1E91F, "M", "𞥁"), + (0x1E920, "M", "𞥂"), + (0x1E921, "M", "𞥃"), + (0x1E922, "V"), + (0x1E94C, "X"), + (0x1E950, "V"), + (0x1E95A, "X"), + (0x1E95E, "V"), + (0x1E960, "X"), + (0x1EC71, "V"), + (0x1ECB5, "X"), + (0x1ED01, "V"), + (0x1ED3E, "X"), + (0x1EE00, "M", "ا"), + (0x1EE01, "M", "ب"), + (0x1EE02, "M", "ج"), + (0x1EE03, "M", "د"), + (0x1EE04, "X"), + (0x1EE05, "M", "و"), + (0x1EE06, "M", "ز"), + (0x1EE07, "M", "ح"), + (0x1EE08, "M", "ط"), + (0x1EE09, "M", "ي"), + (0x1EE0A, "M", "ك"), + (0x1EE0B, "M", "ل"), + (0x1EE0C, "M", "م"), + (0x1EE0D, "M", "ن"), + (0x1EE0E, "M", "س"), + (0x1EE0F, "M", "ع"), + (0x1EE10, "M", "ف"), + (0x1EE11, "M", "ص"), + (0x1EE12, "M", "ق"), + (0x1EE13, "M", "ر"), + (0x1EE14, "M", "ش"), + (0x1EE15, "M", "ت"), + (0x1EE16, "M", "ث"), + (0x1EE17, "M", "خ"), + (0x1EE18, "M", "ذ"), + (0x1EE19, "M", "ض"), + (0x1EE1A, "M", "ظ"), + (0x1EE1B, "M", "غ"), + (0x1EE1C, "M", "ٮ"), + (0x1EE1D, "M", "ں"), + (0x1EE1E, "M", "ڡ"), + (0x1EE1F, "M", "ٯ"), + (0x1EE20, "X"), + (0x1EE21, "M", "ب"), + (0x1EE22, "M", "ج"), + (0x1EE23, "X"), + (0x1EE24, "M", "ه"), + (0x1EE25, "X"), + (0x1EE27, "M", "ح"), + (0x1EE28, "X"), + (0x1EE29, "M", "ي"), + (0x1EE2A, "M", "ك"), + (0x1EE2B, "M", "ل"), + (0x1EE2C, "M", "م"), + (0x1EE2D, "M", "ن"), + (0x1EE2E, "M", "س"), + (0x1EE2F, "M", "ع"), + (0x1EE30, "M", "ف"), + (0x1EE31, "M", "ص"), + (0x1EE32, "M", "ق"), + (0x1EE33, "X"), + (0x1EE34, "M", "ش"), + (0x1EE35, "M", "ت"), + (0x1EE36, "M", "ث"), + (0x1EE37, "M", "خ"), + (0x1EE38, "X"), + (0x1EE39, "M", "ض"), + (0x1EE3A, "X"), + (0x1EE3B, "M", "غ"), + (0x1EE3C, "X"), + (0x1EE42, "M", "ج"), + (0x1EE43, "X"), + (0x1EE47, "M", "ح"), + (0x1EE48, "X"), + (0x1EE49, "M", "ي"), + (0x1EE4A, "X"), + (0x1EE4B, "M", "ل"), + (0x1EE4C, "X"), + (0x1EE4D, "M", "ن"), + (0x1EE4E, "M", "س"), + ] + + +def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EE4F, "M", "ع"), + (0x1EE50, "X"), + (0x1EE51, "M", "ص"), + (0x1EE52, "M", "ق"), + (0x1EE53, "X"), + (0x1EE54, "M", "ش"), + (0x1EE55, "X"), + (0x1EE57, "M", "خ"), + (0x1EE58, "X"), + (0x1EE59, "M", "ض"), + (0x1EE5A, "X"), + (0x1EE5B, "M", "غ"), + (0x1EE5C, "X"), + (0x1EE5D, "M", "ں"), + (0x1EE5E, "X"), + (0x1EE5F, "M", "ٯ"), + (0x1EE60, "X"), + (0x1EE61, "M", "ب"), + (0x1EE62, "M", "ج"), + (0x1EE63, "X"), + (0x1EE64, "M", "ه"), + (0x1EE65, "X"), + (0x1EE67, "M", "ح"), + (0x1EE68, "M", "ط"), + (0x1EE69, "M", "ي"), + (0x1EE6A, "M", "ك"), + (0x1EE6B, "X"), + (0x1EE6C, "M", "م"), + (0x1EE6D, "M", "ن"), + (0x1EE6E, "M", "س"), + (0x1EE6F, "M", "ع"), + (0x1EE70, "M", "ف"), + (0x1EE71, "M", "ص"), + (0x1EE72, "M", "ق"), + (0x1EE73, "X"), + (0x1EE74, "M", "ش"), + (0x1EE75, "M", "ت"), + (0x1EE76, "M", "ث"), + (0x1EE77, "M", "خ"), + (0x1EE78, "X"), + (0x1EE79, "M", "ض"), + (0x1EE7A, "M", "ظ"), + (0x1EE7B, "M", "غ"), + (0x1EE7C, "M", "ٮ"), + (0x1EE7D, "X"), + (0x1EE7E, "M", "ڡ"), + (0x1EE7F, "X"), + (0x1EE80, "M", "ا"), + (0x1EE81, "M", "ب"), + (0x1EE82, "M", "ج"), + (0x1EE83, "M", "د"), + (0x1EE84, "M", "ه"), + (0x1EE85, "M", "و"), + (0x1EE86, "M", "ز"), + (0x1EE87, "M", "ح"), + (0x1EE88, "M", "ط"), + (0x1EE89, "M", "ي"), + (0x1EE8A, "X"), + (0x1EE8B, "M", "ل"), + (0x1EE8C, "M", "م"), + (0x1EE8D, "M", "ن"), + (0x1EE8E, "M", "س"), + (0x1EE8F, "M", "ع"), + (0x1EE90, "M", "ف"), + (0x1EE91, "M", "ص"), + (0x1EE92, "M", "ق"), + (0x1EE93, "M", "ر"), + (0x1EE94, "M", "ش"), + (0x1EE95, "M", "ت"), + (0x1EE96, "M", "ث"), + (0x1EE97, "M", "خ"), + (0x1EE98, "M", "ذ"), + (0x1EE99, "M", "ض"), + (0x1EE9A, "M", "ظ"), + (0x1EE9B, "M", "غ"), + (0x1EE9C, "X"), + (0x1EEA1, "M", "ب"), + (0x1EEA2, "M", "ج"), + (0x1EEA3, "M", "د"), + (0x1EEA4, "X"), + (0x1EEA5, "M", "و"), + (0x1EEA6, "M", "ز"), + (0x1EEA7, "M", "ح"), + (0x1EEA8, "M", "ط"), + (0x1EEA9, "M", "ي"), + (0x1EEAA, "X"), + (0x1EEAB, "M", "ل"), + (0x1EEAC, "M", "م"), + (0x1EEAD, "M", "ن"), + (0x1EEAE, "M", "س"), + (0x1EEAF, "M", "ع"), + (0x1EEB0, "M", "ف"), + (0x1EEB1, "M", "ص"), + (0x1EEB2, "M", "ق"), + (0x1EEB3, "M", "ر"), + (0x1EEB4, "M", "ش"), + (0x1EEB5, "M", "ت"), + (0x1EEB6, "M", "ث"), + (0x1EEB7, "M", "خ"), + (0x1EEB8, "M", "ذ"), + ] + + +def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1EEB9, "M", "ض"), + (0x1EEBA, "M", "ظ"), + (0x1EEBB, "M", "غ"), + (0x1EEBC, "X"), + (0x1EEF0, "V"), + (0x1EEF2, "X"), + (0x1F000, "V"), + (0x1F02C, "X"), + (0x1F030, "V"), + (0x1F094, "X"), + (0x1F0A0, "V"), + (0x1F0AF, "X"), + (0x1F0B1, "V"), + (0x1F0C0, "X"), + (0x1F0C1, "V"), + (0x1F0D0, "X"), + (0x1F0D1, "V"), + (0x1F0F6, "X"), + (0x1F101, "3", "0,"), + (0x1F102, "3", "1,"), + (0x1F103, "3", "2,"), + (0x1F104, "3", "3,"), + (0x1F105, "3", "4,"), + (0x1F106, "3", "5,"), + (0x1F107, "3", "6,"), + (0x1F108, "3", "7,"), + (0x1F109, "3", "8,"), + (0x1F10A, "3", "9,"), + (0x1F10B, "V"), + (0x1F110, "3", "(a)"), + (0x1F111, "3", "(b)"), + (0x1F112, "3", "(c)"), + (0x1F113, "3", "(d)"), + (0x1F114, "3", "(e)"), + (0x1F115, "3", "(f)"), + (0x1F116, "3", "(g)"), + (0x1F117, "3", "(h)"), + (0x1F118, "3", "(i)"), + (0x1F119, "3", "(j)"), + (0x1F11A, "3", "(k)"), + (0x1F11B, "3", "(l)"), + (0x1F11C, "3", "(m)"), + (0x1F11D, "3", "(n)"), + (0x1F11E, "3", "(o)"), + (0x1F11F, "3", "(p)"), + (0x1F120, "3", "(q)"), + (0x1F121, "3", "(r)"), + (0x1F122, "3", "(s)"), + (0x1F123, "3", "(t)"), + (0x1F124, "3", "(u)"), + (0x1F125, "3", "(v)"), + (0x1F126, "3", "(w)"), + (0x1F127, "3", "(x)"), + (0x1F128, "3", "(y)"), + (0x1F129, "3", "(z)"), + (0x1F12A, "M", "〔s〕"), + (0x1F12B, "M", "c"), + (0x1F12C, "M", "r"), + (0x1F12D, "M", "cd"), + (0x1F12E, "M", "wz"), + (0x1F12F, "V"), + (0x1F130, "M", "a"), + (0x1F131, "M", "b"), + (0x1F132, "M", "c"), + (0x1F133, "M", "d"), + (0x1F134, "M", "e"), + (0x1F135, "M", "f"), + (0x1F136, "M", "g"), + (0x1F137, "M", "h"), + (0x1F138, "M", "i"), + (0x1F139, "M", "j"), + (0x1F13A, "M", "k"), + (0x1F13B, "M", "l"), + (0x1F13C, "M", "m"), + (0x1F13D, "M", "n"), + (0x1F13E, "M", "o"), + (0x1F13F, "M", "p"), + (0x1F140, "M", "q"), + (0x1F141, "M", "r"), + (0x1F142, "M", "s"), + (0x1F143, "M", "t"), + (0x1F144, "M", "u"), + (0x1F145, "M", "v"), + (0x1F146, "M", "w"), + (0x1F147, "M", "x"), + (0x1F148, "M", "y"), + (0x1F149, "M", "z"), + (0x1F14A, "M", "hv"), + (0x1F14B, "M", "mv"), + (0x1F14C, "M", "sd"), + (0x1F14D, "M", "ss"), + (0x1F14E, "M", "ppv"), + (0x1F14F, "M", "wc"), + (0x1F150, "V"), + (0x1F16A, "M", "mc"), + (0x1F16B, "M", "md"), + (0x1F16C, "M", "mr"), + (0x1F16D, "V"), + (0x1F190, "M", "dj"), + (0x1F191, "V"), + ] + + +def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1F1AE, "X"), + (0x1F1E6, "V"), + (0x1F200, "M", "ほか"), + (0x1F201, "M", "ココ"), + (0x1F202, "M", "サ"), + (0x1F203, "X"), + (0x1F210, "M", "手"), + (0x1F211, "M", "字"), + (0x1F212, "M", "双"), + (0x1F213, "M", "デ"), + (0x1F214, "M", "二"), + (0x1F215, "M", "多"), + (0x1F216, "M", "解"), + (0x1F217, "M", "天"), + (0x1F218, "M", "交"), + (0x1F219, "M", "映"), + (0x1F21A, "M", "無"), + (0x1F21B, "M", "料"), + (0x1F21C, "M", "前"), + (0x1F21D, "M", "後"), + (0x1F21E, "M", "再"), + (0x1F21F, "M", "新"), + (0x1F220, "M", "初"), + (0x1F221, "M", "終"), + (0x1F222, "M", "生"), + (0x1F223, "M", "販"), + (0x1F224, "M", "声"), + (0x1F225, "M", "吹"), + (0x1F226, "M", "演"), + (0x1F227, "M", "投"), + (0x1F228, "M", "捕"), + (0x1F229, "M", "一"), + (0x1F22A, "M", "三"), + (0x1F22B, "M", "遊"), + (0x1F22C, "M", "左"), + (0x1F22D, "M", "中"), + (0x1F22E, "M", "右"), + (0x1F22F, "M", "指"), + (0x1F230, "M", "走"), + (0x1F231, "M", "打"), + (0x1F232, "M", "禁"), + (0x1F233, "M", "空"), + (0x1F234, "M", "合"), + (0x1F235, "M", "満"), + (0x1F236, "M", "有"), + (0x1F237, "M", "月"), + (0x1F238, "M", "申"), + (0x1F239, "M", "割"), + (0x1F23A, "M", "営"), + (0x1F23B, "M", "配"), + (0x1F23C, "X"), + (0x1F240, "M", "〔本〕"), + (0x1F241, "M", "〔三〕"), + (0x1F242, "M", "〔二〕"), + (0x1F243, "M", "〔安〕"), + (0x1F244, "M", "〔点〕"), + (0x1F245, "M", "〔打〕"), + (0x1F246, "M", "〔盗〕"), + (0x1F247, "M", "〔勝〕"), + (0x1F248, "M", "〔敗〕"), + (0x1F249, "X"), + (0x1F250, "M", "得"), + (0x1F251, "M", "可"), + (0x1F252, "X"), + (0x1F260, "V"), + (0x1F266, "X"), + (0x1F300, "V"), + (0x1F6D8, "X"), + (0x1F6DC, "V"), + (0x1F6ED, "X"), + (0x1F6F0, "V"), + (0x1F6FD, "X"), + (0x1F700, "V"), + (0x1F777, "X"), + (0x1F77B, "V"), + (0x1F7DA, "X"), + (0x1F7E0, "V"), + (0x1F7EC, "X"), + (0x1F7F0, "V"), + (0x1F7F1, "X"), + (0x1F800, "V"), + (0x1F80C, "X"), + (0x1F810, "V"), + (0x1F848, "X"), + (0x1F850, "V"), + (0x1F85A, "X"), + (0x1F860, "V"), + (0x1F888, "X"), + (0x1F890, "V"), + (0x1F8AE, "X"), + (0x1F8B0, "V"), + (0x1F8B2, "X"), + (0x1F900, "V"), + (0x1FA54, "X"), + (0x1FA60, "V"), + (0x1FA6E, "X"), + (0x1FA70, "V"), + (0x1FA7D, "X"), + (0x1FA80, "V"), + (0x1FA89, "X"), + ] + + +def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x1FA90, "V"), + (0x1FABE, "X"), + (0x1FABF, "V"), + (0x1FAC6, "X"), + (0x1FACE, "V"), + (0x1FADC, "X"), + (0x1FAE0, "V"), + (0x1FAE9, "X"), + (0x1FAF0, "V"), + (0x1FAF9, "X"), + (0x1FB00, "V"), + (0x1FB93, "X"), + (0x1FB94, "V"), + (0x1FBCB, "X"), + (0x1FBF0, "M", "0"), + (0x1FBF1, "M", "1"), + (0x1FBF2, "M", "2"), + (0x1FBF3, "M", "3"), + (0x1FBF4, "M", "4"), + (0x1FBF5, "M", "5"), + (0x1FBF6, "M", "6"), + (0x1FBF7, "M", "7"), + (0x1FBF8, "M", "8"), + (0x1FBF9, "M", "9"), + (0x1FBFA, "X"), + (0x20000, "V"), + (0x2A6E0, "X"), + (0x2A700, "V"), + (0x2B73A, "X"), + (0x2B740, "V"), + (0x2B81E, "X"), + (0x2B820, "V"), + (0x2CEA2, "X"), + (0x2CEB0, "V"), + (0x2EBE1, "X"), + (0x2EBF0, "V"), + (0x2EE5E, "X"), + (0x2F800, "M", "丽"), + (0x2F801, "M", "丸"), + (0x2F802, "M", "乁"), + (0x2F803, "M", "𠄢"), + (0x2F804, "M", "你"), + (0x2F805, "M", "侮"), + (0x2F806, "M", "侻"), + (0x2F807, "M", "倂"), + (0x2F808, "M", "偺"), + (0x2F809, "M", "備"), + (0x2F80A, "M", "僧"), + (0x2F80B, "M", "像"), + (0x2F80C, "M", "㒞"), + (0x2F80D, "M", "𠘺"), + (0x2F80E, "M", "免"), + (0x2F80F, "M", "兔"), + (0x2F810, "M", "兤"), + (0x2F811, "M", "具"), + (0x2F812, "M", "𠔜"), + (0x2F813, "M", "㒹"), + (0x2F814, "M", "內"), + (0x2F815, "M", "再"), + (0x2F816, "M", "𠕋"), + (0x2F817, "M", "冗"), + (0x2F818, "M", "冤"), + (0x2F819, "M", "仌"), + (0x2F81A, "M", "冬"), + (0x2F81B, "M", "况"), + (0x2F81C, "M", "𩇟"), + (0x2F81D, "M", "凵"), + (0x2F81E, "M", "刃"), + (0x2F81F, "M", "㓟"), + (0x2F820, "M", "刻"), + (0x2F821, "M", "剆"), + (0x2F822, "M", "割"), + (0x2F823, "M", "剷"), + (0x2F824, "M", "㔕"), + (0x2F825, "M", "勇"), + (0x2F826, "M", "勉"), + (0x2F827, "M", "勤"), + (0x2F828, "M", "勺"), + (0x2F829, "M", "包"), + (0x2F82A, "M", "匆"), + (0x2F82B, "M", "北"), + (0x2F82C, "M", "卉"), + (0x2F82D, "M", "卑"), + (0x2F82E, "M", "博"), + (0x2F82F, "M", "即"), + (0x2F830, "M", "卽"), + (0x2F831, "M", "卿"), + (0x2F834, "M", "𠨬"), + (0x2F835, "M", "灰"), + (0x2F836, "M", "及"), + (0x2F837, "M", "叟"), + (0x2F838, "M", "𠭣"), + (0x2F839, "M", "叫"), + (0x2F83A, "M", "叱"), + (0x2F83B, "M", "吆"), + (0x2F83C, "M", "咞"), + (0x2F83D, "M", "吸"), + (0x2F83E, "M", "呈"), + (0x2F83F, "M", "周"), + (0x2F840, "M", "咢"), + ] + + +def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F841, "M", "哶"), + (0x2F842, "M", "唐"), + (0x2F843, "M", "啓"), + (0x2F844, "M", "啣"), + (0x2F845, "M", "善"), + (0x2F847, "M", "喙"), + (0x2F848, "M", "喫"), + (0x2F849, "M", "喳"), + (0x2F84A, "M", "嗂"), + (0x2F84B, "M", "圖"), + (0x2F84C, "M", "嘆"), + (0x2F84D, "M", "圗"), + (0x2F84E, "M", "噑"), + (0x2F84F, "M", "噴"), + (0x2F850, "M", "切"), + (0x2F851, "M", "壮"), + (0x2F852, "M", "城"), + (0x2F853, "M", "埴"), + (0x2F854, "M", "堍"), + (0x2F855, "M", "型"), + (0x2F856, "M", "堲"), + (0x2F857, "M", "報"), + (0x2F858, "M", "墬"), + (0x2F859, "M", "𡓤"), + (0x2F85A, "M", "売"), + (0x2F85B, "M", "壷"), + (0x2F85C, "M", "夆"), + (0x2F85D, "M", "多"), + (0x2F85E, "M", "夢"), + (0x2F85F, "M", "奢"), + (0x2F860, "M", "𡚨"), + (0x2F861, "M", "𡛪"), + (0x2F862, "M", "姬"), + (0x2F863, "M", "娛"), + (0x2F864, "M", "娧"), + (0x2F865, "M", "姘"), + (0x2F866, "M", "婦"), + (0x2F867, "M", "㛮"), + (0x2F868, "X"), + (0x2F869, "M", "嬈"), + (0x2F86A, "M", "嬾"), + (0x2F86C, "M", "𡧈"), + (0x2F86D, "M", "寃"), + (0x2F86E, "M", "寘"), + (0x2F86F, "M", "寧"), + (0x2F870, "M", "寳"), + (0x2F871, "M", "𡬘"), + (0x2F872, "M", "寿"), + (0x2F873, "M", "将"), + (0x2F874, "X"), + (0x2F875, "M", "尢"), + (0x2F876, "M", "㞁"), + (0x2F877, "M", "屠"), + (0x2F878, "M", "屮"), + (0x2F879, "M", "峀"), + (0x2F87A, "M", "岍"), + (0x2F87B, "M", "𡷤"), + (0x2F87C, "M", "嵃"), + (0x2F87D, "M", "𡷦"), + (0x2F87E, "M", "嵮"), + (0x2F87F, "M", "嵫"), + (0x2F880, "M", "嵼"), + (0x2F881, "M", "巡"), + (0x2F882, "M", "巢"), + (0x2F883, "M", "㠯"), + (0x2F884, "M", "巽"), + (0x2F885, "M", "帨"), + (0x2F886, "M", "帽"), + (0x2F887, "M", "幩"), + (0x2F888, "M", "㡢"), + (0x2F889, "M", "𢆃"), + (0x2F88A, "M", "㡼"), + (0x2F88B, "M", "庰"), + (0x2F88C, "M", "庳"), + (0x2F88D, "M", "庶"), + (0x2F88E, "M", "廊"), + (0x2F88F, "M", "𪎒"), + (0x2F890, "M", "廾"), + (0x2F891, "M", "𢌱"), + (0x2F893, "M", "舁"), + (0x2F894, "M", "弢"), + (0x2F896, "M", "㣇"), + (0x2F897, "M", "𣊸"), + (0x2F898, "M", "𦇚"), + (0x2F899, "M", "形"), + (0x2F89A, "M", "彫"), + (0x2F89B, "M", "㣣"), + (0x2F89C, "M", "徚"), + (0x2F89D, "M", "忍"), + (0x2F89E, "M", "志"), + (0x2F89F, "M", "忹"), + (0x2F8A0, "M", "悁"), + (0x2F8A1, "M", "㤺"), + (0x2F8A2, "M", "㤜"), + (0x2F8A3, "M", "悔"), + (0x2F8A4, "M", "𢛔"), + (0x2F8A5, "M", "惇"), + (0x2F8A6, "M", "慈"), + (0x2F8A7, "M", "慌"), + (0x2F8A8, "M", "慎"), + ] + + +def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F8A9, "M", "慌"), + (0x2F8AA, "M", "慺"), + (0x2F8AB, "M", "憎"), + (0x2F8AC, "M", "憲"), + (0x2F8AD, "M", "憤"), + (0x2F8AE, "M", "憯"), + (0x2F8AF, "M", "懞"), + (0x2F8B0, "M", "懲"), + (0x2F8B1, "M", "懶"), + (0x2F8B2, "M", "成"), + (0x2F8B3, "M", "戛"), + (0x2F8B4, "M", "扝"), + (0x2F8B5, "M", "抱"), + (0x2F8B6, "M", "拔"), + (0x2F8B7, "M", "捐"), + (0x2F8B8, "M", "𢬌"), + (0x2F8B9, "M", "挽"), + (0x2F8BA, "M", "拼"), + (0x2F8BB, "M", "捨"), + (0x2F8BC, "M", "掃"), + (0x2F8BD, "M", "揤"), + (0x2F8BE, "M", "𢯱"), + (0x2F8BF, "M", "搢"), + (0x2F8C0, "M", "揅"), + (0x2F8C1, "M", "掩"), + (0x2F8C2, "M", "㨮"), + (0x2F8C3, "M", "摩"), + (0x2F8C4, "M", "摾"), + (0x2F8C5, "M", "撝"), + (0x2F8C6, "M", "摷"), + (0x2F8C7, "M", "㩬"), + (0x2F8C8, "M", "敏"), + (0x2F8C9, "M", "敬"), + (0x2F8CA, "M", "𣀊"), + (0x2F8CB, "M", "旣"), + (0x2F8CC, "M", "書"), + (0x2F8CD, "M", "晉"), + (0x2F8CE, "M", "㬙"), + (0x2F8CF, "M", "暑"), + (0x2F8D0, "M", "㬈"), + (0x2F8D1, "M", "㫤"), + (0x2F8D2, "M", "冒"), + (0x2F8D3, "M", "冕"), + (0x2F8D4, "M", "最"), + (0x2F8D5, "M", "暜"), + (0x2F8D6, "M", "肭"), + (0x2F8D7, "M", "䏙"), + (0x2F8D8, "M", "朗"), + (0x2F8D9, "M", "望"), + (0x2F8DA, "M", "朡"), + (0x2F8DB, "M", "杞"), + (0x2F8DC, "M", "杓"), + (0x2F8DD, "M", "𣏃"), + (0x2F8DE, "M", "㭉"), + (0x2F8DF, "M", "柺"), + (0x2F8E0, "M", "枅"), + (0x2F8E1, "M", "桒"), + (0x2F8E2, "M", "梅"), + (0x2F8E3, "M", "𣑭"), + (0x2F8E4, "M", "梎"), + (0x2F8E5, "M", "栟"), + (0x2F8E6, "M", "椔"), + (0x2F8E7, "M", "㮝"), + (0x2F8E8, "M", "楂"), + (0x2F8E9, "M", "榣"), + (0x2F8EA, "M", "槪"), + (0x2F8EB, "M", "檨"), + (0x2F8EC, "M", "𣚣"), + (0x2F8ED, "M", "櫛"), + (0x2F8EE, "M", "㰘"), + (0x2F8EF, "M", "次"), + (0x2F8F0, "M", "𣢧"), + (0x2F8F1, "M", "歔"), + (0x2F8F2, "M", "㱎"), + (0x2F8F3, "M", "歲"), + (0x2F8F4, "M", "殟"), + (0x2F8F5, "M", "殺"), + (0x2F8F6, "M", "殻"), + (0x2F8F7, "M", "𣪍"), + (0x2F8F8, "M", "𡴋"), + (0x2F8F9, "M", "𣫺"), + (0x2F8FA, "M", "汎"), + (0x2F8FB, "M", "𣲼"), + (0x2F8FC, "M", "沿"), + (0x2F8FD, "M", "泍"), + (0x2F8FE, "M", "汧"), + (0x2F8FF, "M", "洖"), + (0x2F900, "M", "派"), + (0x2F901, "M", "海"), + (0x2F902, "M", "流"), + (0x2F903, "M", "浩"), + (0x2F904, "M", "浸"), + (0x2F905, "M", "涅"), + (0x2F906, "M", "𣴞"), + (0x2F907, "M", "洴"), + (0x2F908, "M", "港"), + (0x2F909, "M", "湮"), + (0x2F90A, "M", "㴳"), + (0x2F90B, "M", "滋"), + (0x2F90C, "M", "滇"), + ] + + +def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F90D, "M", "𣻑"), + (0x2F90E, "M", "淹"), + (0x2F90F, "M", "潮"), + (0x2F910, "M", "𣽞"), + (0x2F911, "M", "𣾎"), + (0x2F912, "M", "濆"), + (0x2F913, "M", "瀹"), + (0x2F914, "M", "瀞"), + (0x2F915, "M", "瀛"), + (0x2F916, "M", "㶖"), + (0x2F917, "M", "灊"), + (0x2F918, "M", "災"), + (0x2F919, "M", "灷"), + (0x2F91A, "M", "炭"), + (0x2F91B, "M", "𠔥"), + (0x2F91C, "M", "煅"), + (0x2F91D, "M", "𤉣"), + (0x2F91E, "M", "熜"), + (0x2F91F, "X"), + (0x2F920, "M", "爨"), + (0x2F921, "M", "爵"), + (0x2F922, "M", "牐"), + (0x2F923, "M", "𤘈"), + (0x2F924, "M", "犀"), + (0x2F925, "M", "犕"), + (0x2F926, "M", "𤜵"), + (0x2F927, "M", "𤠔"), + (0x2F928, "M", "獺"), + (0x2F929, "M", "王"), + (0x2F92A, "M", "㺬"), + (0x2F92B, "M", "玥"), + (0x2F92C, "M", "㺸"), + (0x2F92E, "M", "瑇"), + (0x2F92F, "M", "瑜"), + (0x2F930, "M", "瑱"), + (0x2F931, "M", "璅"), + (0x2F932, "M", "瓊"), + (0x2F933, "M", "㼛"), + (0x2F934, "M", "甤"), + (0x2F935, "M", "𤰶"), + (0x2F936, "M", "甾"), + (0x2F937, "M", "𤲒"), + (0x2F938, "M", "異"), + (0x2F939, "M", "𢆟"), + (0x2F93A, "M", "瘐"), + (0x2F93B, "M", "𤾡"), + (0x2F93C, "M", "𤾸"), + (0x2F93D, "M", "𥁄"), + (0x2F93E, "M", "㿼"), + (0x2F93F, "M", "䀈"), + (0x2F940, "M", "直"), + (0x2F941, "M", "𥃳"), + (0x2F942, "M", "𥃲"), + (0x2F943, "M", "𥄙"), + (0x2F944, "M", "𥄳"), + (0x2F945, "M", "眞"), + (0x2F946, "M", "真"), + (0x2F948, "M", "睊"), + (0x2F949, "M", "䀹"), + (0x2F94A, "M", "瞋"), + (0x2F94B, "M", "䁆"), + (0x2F94C, "M", "䂖"), + (0x2F94D, "M", "𥐝"), + (0x2F94E, "M", "硎"), + (0x2F94F, "M", "碌"), + (0x2F950, "M", "磌"), + (0x2F951, "M", "䃣"), + (0x2F952, "M", "𥘦"), + (0x2F953, "M", "祖"), + (0x2F954, "M", "𥚚"), + (0x2F955, "M", "𥛅"), + (0x2F956, "M", "福"), + (0x2F957, "M", "秫"), + (0x2F958, "M", "䄯"), + (0x2F959, "M", "穀"), + (0x2F95A, "M", "穊"), + (0x2F95B, "M", "穏"), + (0x2F95C, "M", "𥥼"), + (0x2F95D, "M", "𥪧"), + (0x2F95F, "X"), + (0x2F960, "M", "䈂"), + (0x2F961, "M", "𥮫"), + (0x2F962, "M", "篆"), + (0x2F963, "M", "築"), + (0x2F964, "M", "䈧"), + (0x2F965, "M", "𥲀"), + (0x2F966, "M", "糒"), + (0x2F967, "M", "䊠"), + (0x2F968, "M", "糨"), + (0x2F969, "M", "糣"), + (0x2F96A, "M", "紀"), + (0x2F96B, "M", "𥾆"), + (0x2F96C, "M", "絣"), + (0x2F96D, "M", "䌁"), + (0x2F96E, "M", "緇"), + (0x2F96F, "M", "縂"), + (0x2F970, "M", "繅"), + (0x2F971, "M", "䌴"), + (0x2F972, "M", "𦈨"), + (0x2F973, "M", "𦉇"), + ] + + +def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F974, "M", "䍙"), + (0x2F975, "M", "𦋙"), + (0x2F976, "M", "罺"), + (0x2F977, "M", "𦌾"), + (0x2F978, "M", "羕"), + (0x2F979, "M", "翺"), + (0x2F97A, "M", "者"), + (0x2F97B, "M", "𦓚"), + (0x2F97C, "M", "𦔣"), + (0x2F97D, "M", "聠"), + (0x2F97E, "M", "𦖨"), + (0x2F97F, "M", "聰"), + (0x2F980, "M", "𣍟"), + (0x2F981, "M", "䏕"), + (0x2F982, "M", "育"), + (0x2F983, "M", "脃"), + (0x2F984, "M", "䐋"), + (0x2F985, "M", "脾"), + (0x2F986, "M", "媵"), + (0x2F987, "M", "𦞧"), + (0x2F988, "M", "𦞵"), + (0x2F989, "M", "𣎓"), + (0x2F98A, "M", "𣎜"), + (0x2F98B, "M", "舁"), + (0x2F98C, "M", "舄"), + (0x2F98D, "M", "辞"), + (0x2F98E, "M", "䑫"), + (0x2F98F, "M", "芑"), + (0x2F990, "M", "芋"), + (0x2F991, "M", "芝"), + (0x2F992, "M", "劳"), + (0x2F993, "M", "花"), + (0x2F994, "M", "芳"), + (0x2F995, "M", "芽"), + (0x2F996, "M", "苦"), + (0x2F997, "M", "𦬼"), + (0x2F998, "M", "若"), + (0x2F999, "M", "茝"), + (0x2F99A, "M", "荣"), + (0x2F99B, "M", "莭"), + (0x2F99C, "M", "茣"), + (0x2F99D, "M", "莽"), + (0x2F99E, "M", "菧"), + (0x2F99F, "M", "著"), + (0x2F9A0, "M", "荓"), + (0x2F9A1, "M", "菊"), + (0x2F9A2, "M", "菌"), + (0x2F9A3, "M", "菜"), + (0x2F9A4, "M", "𦰶"), + (0x2F9A5, "M", "𦵫"), + (0x2F9A6, "M", "𦳕"), + (0x2F9A7, "M", "䔫"), + (0x2F9A8, "M", "蓱"), + (0x2F9A9, "M", "蓳"), + (0x2F9AA, "M", "蔖"), + (0x2F9AB, "M", "𧏊"), + (0x2F9AC, "M", "蕤"), + (0x2F9AD, "M", "𦼬"), + (0x2F9AE, "M", "䕝"), + (0x2F9AF, "M", "䕡"), + (0x2F9B0, "M", "𦾱"), + (0x2F9B1, "M", "𧃒"), + (0x2F9B2, "M", "䕫"), + (0x2F9B3, "M", "虐"), + (0x2F9B4, "M", "虜"), + (0x2F9B5, "M", "虧"), + (0x2F9B6, "M", "虩"), + (0x2F9B7, "M", "蚩"), + (0x2F9B8, "M", "蚈"), + (0x2F9B9, "M", "蜎"), + (0x2F9BA, "M", "蛢"), + (0x2F9BB, "M", "蝹"), + (0x2F9BC, "M", "蜨"), + (0x2F9BD, "M", "蝫"), + (0x2F9BE, "M", "螆"), + (0x2F9BF, "X"), + (0x2F9C0, "M", "蟡"), + (0x2F9C1, "M", "蠁"), + (0x2F9C2, "M", "䗹"), + (0x2F9C3, "M", "衠"), + (0x2F9C4, "M", "衣"), + (0x2F9C5, "M", "𧙧"), + (0x2F9C6, "M", "裗"), + (0x2F9C7, "M", "裞"), + (0x2F9C8, "M", "䘵"), + (0x2F9C9, "M", "裺"), + (0x2F9CA, "M", "㒻"), + (0x2F9CB, "M", "𧢮"), + (0x2F9CC, "M", "𧥦"), + (0x2F9CD, "M", "䚾"), + (0x2F9CE, "M", "䛇"), + (0x2F9CF, "M", "誠"), + (0x2F9D0, "M", "諭"), + (0x2F9D1, "M", "變"), + (0x2F9D2, "M", "豕"), + (0x2F9D3, "M", "𧲨"), + (0x2F9D4, "M", "貫"), + (0x2F9D5, "M", "賁"), + (0x2F9D6, "M", "贛"), + (0x2F9D7, "M", "起"), + ] + + +def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ + (0x2F9D8, "M", "𧼯"), + (0x2F9D9, "M", "𠠄"), + (0x2F9DA, "M", "跋"), + (0x2F9DB, "M", "趼"), + (0x2F9DC, "M", "跰"), + (0x2F9DD, "M", "𠣞"), + (0x2F9DE, "M", "軔"), + (0x2F9DF, "M", "輸"), + (0x2F9E0, "M", "𨗒"), + (0x2F9E1, "M", "𨗭"), + (0x2F9E2, "M", "邔"), + (0x2F9E3, "M", "郱"), + (0x2F9E4, "M", "鄑"), + (0x2F9E5, "M", "𨜮"), + (0x2F9E6, "M", "鄛"), + (0x2F9E7, "M", "鈸"), + (0x2F9E8, "M", "鋗"), + (0x2F9E9, "M", "鋘"), + (0x2F9EA, "M", "鉼"), + (0x2F9EB, "M", "鏹"), + (0x2F9EC, "M", "鐕"), + (0x2F9ED, "M", "𨯺"), + (0x2F9EE, "M", "開"), + (0x2F9EF, "M", "䦕"), + (0x2F9F0, "M", "閷"), + (0x2F9F1, "M", "𨵷"), + (0x2F9F2, "M", "䧦"), + (0x2F9F3, "M", "雃"), + (0x2F9F4, "M", "嶲"), + (0x2F9F5, "M", "霣"), + (0x2F9F6, "M", "𩅅"), + (0x2F9F7, "M", "𩈚"), + (0x2F9F8, "M", "䩮"), + (0x2F9F9, "M", "䩶"), + (0x2F9FA, "M", "韠"), + (0x2F9FB, "M", "𩐊"), + (0x2F9FC, "M", "䪲"), + (0x2F9FD, "M", "𩒖"), + (0x2F9FE, "M", "頋"), + (0x2FA00, "M", "頩"), + (0x2FA01, "M", "𩖶"), + (0x2FA02, "M", "飢"), + (0x2FA03, "M", "䬳"), + (0x2FA04, "M", "餩"), + (0x2FA05, "M", "馧"), + (0x2FA06, "M", "駂"), + (0x2FA07, "M", "駾"), + (0x2FA08, "M", "䯎"), + (0x2FA09, "M", "𩬰"), + (0x2FA0A, "M", "鬒"), + (0x2FA0B, "M", "鱀"), + (0x2FA0C, "M", "鳽"), + (0x2FA0D, "M", "䳎"), + (0x2FA0E, "M", "䳭"), + (0x2FA0F, "M", "鵧"), + (0x2FA10, "M", "𪃎"), + (0x2FA11, "M", "䳸"), + (0x2FA12, "M", "𪄅"), + (0x2FA13, "M", "𪈎"), + (0x2FA14, "M", "𪊑"), + (0x2FA15, "M", "麻"), + (0x2FA16, "M", "䵖"), + (0x2FA17, "M", "黹"), + (0x2FA18, "M", "黾"), + (0x2FA19, "M", "鼅"), + (0x2FA1A, "M", "鼏"), + (0x2FA1B, "M", "鼖"), + (0x2FA1C, "M", "鼻"), + (0x2FA1D, "M", "𪘀"), + (0x2FA1E, "X"), + (0x30000, "V"), + (0x3134B, "X"), + (0x31350, "V"), + (0x323B0, "X"), + (0xE0100, "I"), + (0xE01F0, "X"), + ] + + +uts46data = tuple( + _seg_0() + + _seg_1() + + _seg_2() + + _seg_3() + + _seg_4() + + _seg_5() + + _seg_6() + + _seg_7() + + _seg_8() + + _seg_9() + + _seg_10() + + _seg_11() + + _seg_12() + + _seg_13() + + _seg_14() + + _seg_15() + + _seg_16() + + _seg_17() + + _seg_18() + + _seg_19() + + _seg_20() + + _seg_21() + + _seg_22() + + _seg_23() + + _seg_24() + + _seg_25() + + _seg_26() + + _seg_27() + + _seg_28() + + _seg_29() + + _seg_30() + + _seg_31() + + _seg_32() + + _seg_33() + + _seg_34() + + _seg_35() + + _seg_36() + + _seg_37() + + _seg_38() + + _seg_39() + + _seg_40() + + _seg_41() + + _seg_42() + + _seg_43() + + _seg_44() + + _seg_45() + + _seg_46() + + _seg_47() + + _seg_48() + + _seg_49() + + _seg_50() + + _seg_51() + + _seg_52() + + _seg_53() + + _seg_54() + + _seg_55() + + _seg_56() + + _seg_57() + + _seg_58() + + _seg_59() + + _seg_60() + + _seg_61() + + _seg_62() + + _seg_63() + + _seg_64() + + _seg_65() + + _seg_66() + + _seg_67() + + _seg_68() + + _seg_69() + + _seg_70() + + _seg_71() + + _seg_72() + + _seg_73() + + _seg_74() + + _seg_75() + + _seg_76() + + _seg_77() + + _seg_78() + + _seg_79() + + _seg_80() + + _seg_81() +) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...] diff --git a/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/INSTALLER b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/METADATA b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/METADATA new file mode 100644 index 0000000..3a8ef46 --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/METADATA @@ -0,0 +1,81 @@ +Metadata-Version: 2.4 +Name: iniconfig +Version: 2.1.0 +Summary: brain-dead simple config-ini parsing +Project-URL: Homepage, https://github.com/pytest-dev/iniconfig +Author-email: Ronny Pfannschmidt , Holger Krekel +License-Expression: MIT +License-File: LICENSE +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst + +iniconfig: brain-dead simple parsing of ini files +======================================================= + +iniconfig is a small and simple INI-file parser module +having a unique set of features: + +* maintains order of sections and entries +* supports multi-line values with or without line-continuations +* supports "#" comments everywhere +* raises errors with proper line-numbers +* no bells and whistles like automatic substitutions +* iniconfig raises an Error if two sections have the same name. + +If you encounter issues or have feature wishes please report them to: + + https://github.com/RonnyPfannschmidt/iniconfig/issues + +Basic Example +=================================== + +If you have an ini file like this: + +.. code-block:: ini + + # content of example.ini + [section1] # comment + name1=value1 # comment + name1b=value1,value2 # comment + + [section2] + name2= + line1 + line2 + +then you can do: + +.. code-block:: pycon + + >>> import iniconfig + >>> ini = iniconfig.IniConfig("example.ini") + >>> ini['section1']['name1'] # raises KeyError if not exists + 'value1' + >>> ini.get('section1', 'name1b', [], lambda x: x.split(",")) + ['value1', 'value2'] + >>> ini.get('section1', 'notexist', [], lambda x: x.split(",")) + [] + >>> [x.name for x in list(ini)] + ['section1', 'section2'] + >>> list(list(ini)[0].items()) + [('name1', 'value1'), ('name1b', 'value1,value2')] + >>> 'section1' in ini + True + >>> 'inexistendsection' in ini + False diff --git a/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/RECORD b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/RECORD new file mode 100644 index 0000000..0207deb --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/RECORD @@ -0,0 +1,14 @@ +iniconfig-2.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +iniconfig-2.1.0.dist-info/METADATA,sha256=uS-Ec4h2hMZZFTrbd_4EGKcxBQHnQ3CfwSYjzQPn5cs,2651 +iniconfig-2.1.0.dist-info/RECORD,, +iniconfig-2.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +iniconfig-2.1.0.dist-info/licenses/LICENSE,sha256=NAn6kfes5VeJRjJnZlbjImT-XvdYFTVyXcmiN3RVG9Q,1098 +iniconfig/__init__.py,sha256=H1UqjEmX-GytGCsqCafTLG-q1CPc_okvCKGairRFMq0,5462 +iniconfig/__pycache__/__init__.cpython-312.pyc,, +iniconfig/__pycache__/_parse.cpython-312.pyc,, +iniconfig/__pycache__/_version.cpython-312.pyc,, +iniconfig/__pycache__/exceptions.cpython-312.pyc,, +iniconfig/_parse.py,sha256=OWGLbmE8GjxcoMWTvnGbck1RoNsTm5bt5ficIRZqWJ8,2436 +iniconfig/_version.py,sha256=dseuoOPG9WZ1Ezr1SC3wS9_hczkX-b1NdE4TQPHFJso,511 +iniconfig/exceptions.py,sha256=BJguifCkPayz-n0hI2D5ym1USoAWYNIdi05Jc4r2r4o,490 +iniconfig/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/WHEEL b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/WHEEL new file mode 100644 index 0000000..12228d4 --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/licenses/LICENSE b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..46f4b28 --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig-2.1.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2010 - 2023 Holger Krekel and others + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.venv/Lib/site-packages/iniconfig/__init__.py b/.venv/Lib/site-packages/iniconfig/__init__.py new file mode 100644 index 0000000..3c40bc9 --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig/__init__.py @@ -0,0 +1,216 @@ +""" brain-dead simple parser for ini-style files. +(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed +""" +from __future__ import annotations +from typing import ( + Callable, + Iterator, + Mapping, + Optional, + Tuple, + TypeVar, + Union, + TYPE_CHECKING, + NoReturn, + NamedTuple, + overload, + cast, +) + +import os + +if TYPE_CHECKING: + from typing import Final + +__all__ = ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"] + +from .exceptions import ParseError +from . import _parse +from ._parse import COMMENTCHARS, iscommentline + +_D = TypeVar("_D") +_T = TypeVar("_T") + + +class SectionWrapper: + config: Final[IniConfig] + name: Final[str] + + def __init__(self, config: IniConfig, name: str) -> None: + self.config = config + self.name = name + + def lineof(self, name: str) -> int | None: + return self.config.lineof(self.name, name) + + @overload + def get(self, key: str) -> str | None: + ... + + @overload + def get( + self, + key: str, + convert: Callable[[str], _T], + ) -> _T | None: + ... + + @overload + def get( + self, + key: str, + default: None, + convert: Callable[[str], _T], + ) -> _T | None: + ... + + @overload + def get(self, key: str, default: _D, convert: None = None) -> str | _D: + ... + + @overload + def get( + self, + key: str, + default: _D, + convert: Callable[[str], _T], + ) -> _T | _D: + ... + + # TODO: investigate possible mypy bug wrt matching the passed over data + def get( # type: ignore [misc] + self, + key: str, + default: _D | None = None, + convert: Callable[[str], _T] | None = None, + ) -> _D | _T | str | None: + return self.config.get(self.name, key, convert=convert, default=default) + + def __getitem__(self, key: str) -> str: + return self.config.sections[self.name][key] + + def __iter__(self) -> Iterator[str]: + section: Mapping[str, str] = self.config.sections.get(self.name, {}) + + def lineof(key: str) -> int: + return self.config.lineof(self.name, key) # type: ignore[return-value] + + yield from sorted(section, key=lineof) + + def items(self) -> Iterator[tuple[str, str]]: + for name in self: + yield name, self[name] + + +class IniConfig: + path: Final[str] + sections: Final[Mapping[str, Mapping[str, str]]] + + def __init__( + self, + path: str | os.PathLike[str], + data: str | None = None, + encoding: str = "utf-8", + ) -> None: + self.path = os.fspath(path) + if data is None: + with open(self.path, encoding=encoding) as fp: + data = fp.read() + + tokens = _parse.parse_lines(self.path, data.splitlines(True)) + + self._sources = {} + sections_data: dict[str, dict[str, str]] + self.sections = sections_data = {} + + for lineno, section, name, value in tokens: + if section is None: + raise ParseError(self.path, lineno, "no section header defined") + self._sources[section, name] = lineno + if name is None: + if section in self.sections: + raise ParseError( + self.path, lineno, f"duplicate section {section!r}" + ) + sections_data[section] = {} + else: + if name in self.sections[section]: + raise ParseError(self.path, lineno, f"duplicate name {name!r}") + assert value is not None + sections_data[section][name] = value + + def lineof(self, section: str, name: str | None = None) -> int | None: + lineno = self._sources.get((section, name)) + return None if lineno is None else lineno + 1 + + @overload + def get( + self, + section: str, + name: str, + ) -> str | None: + ... + + @overload + def get( + self, + section: str, + name: str, + convert: Callable[[str], _T], + ) -> _T | None: + ... + + @overload + def get( + self, + section: str, + name: str, + default: None, + convert: Callable[[str], _T], + ) -> _T | None: + ... + + @overload + def get( + self, section: str, name: str, default: _D, convert: None = None + ) -> str | _D: + ... + + @overload + def get( + self, + section: str, + name: str, + default: _D, + convert: Callable[[str], _T], + ) -> _T | _D: + ... + + def get( # type: ignore + self, + section: str, + name: str, + default: _D | None = None, + convert: Callable[[str], _T] | None = None, + ) -> _D | _T | str | None: + try: + value: str = self.sections[section][name] + except KeyError: + return default + else: + if convert is not None: + return convert(value) + else: + return value + + def __getitem__(self, name: str) -> SectionWrapper: + if name not in self.sections: + raise KeyError(name) + return SectionWrapper(self, name) + + def __iter__(self) -> Iterator[SectionWrapper]: + for name in sorted(self.sections, key=self.lineof): # type: ignore + yield SectionWrapper(self, name) + + def __contains__(self, arg: str) -> bool: + return arg in self.sections diff --git a/.venv/Lib/site-packages/iniconfig/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/iniconfig/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66f6f771d98c0ff3cc3250d4c04cf9faa0f6911f GIT binary patch literal 8913 zcmcIKTWlQHb$4cWc4uGQC!ZpvH7UwkiKJ|aQf=9?A(MJAk|;`~-D*W)v)UQTOYVcc zGn7nv8L^I<(1lw|H5yVW0#PYYkx+~XXg}PKq79G_0|knu*+QGCf-((SG(QEcRiKn# zJ?GBs>~cjt0yIPL&Y649x%b?2?|G8{|sxZ(o<=7>b1 zMB*eq#ijWu&mn9{S<=?1H7!Jiv@L2&+oSfh7!}iws3Yx+I@7MGEA5WD)1Ig&T@$TI zd!t@PXHEH{KG{WWFoyth_@jP~Tq2U~hg>uu*(EU*OoyT&z_m+`RBgI0TE}2#sxDn0 ztxq>Z8#uxf*&(^^aypmMM)vJCznge+j7XkqM5?*V8#r57pBMUkW?%E9HR7L=!xw2h zndy||xD-~B>G6~t9*7=A~gpX%amw@I3mB-{%xU(~Ss&6QqN+#rtB1_I+As-P=a{)A;%Ff;*dY4)MXarwPC)r5C5Frayc&J7%i z^&CIabE2>Rm?rjT&&uiq&1la4cv_ZpA~Aberm1XP(yWQNqV@w5!qFtm8nJ4QzD%+w zn;A)7(wqah=OdH?2kxHJr%oN|AL==N`0SwONh*nKIxT0^R5By~3Z)+5Xh7eOEyiRO z;b{;$Xb2jOkM+XuPy%4{F>loWK-V0RNtA@5BzGkh>uW5p z%f6f7rU_BIv_+}`UPY-{^1|C8wMagAJ13oyty;~XoWP^}eF{e_(Q z)-^t#p*pfH+>4zVGc;EZdviGHh?RO%#Ba1^T-o1HoFKq*i4f}&{eSgU8 z92~386)||wEMxLyB@deAlB`Yzs(66DE}$<8(9Dok(WNg`Efyxo4GYl-P(&>HesK?) z9ZnQ1idwoo`p!FGXtP!KLVwW?%?w#{6Ng)07ni7fp0e(ewQ^la9*Iw+RJsQ!6`TRC zt|9lQt$q-&N~_nDR_{-|;PMoif5DY{0F`!P(+17&VufQkdH1F(*r1n*a0r0^4;{!^ za{Q0DyM~+wNrXpJf`X^}pn?0q%nFK~DZy_esWU*Nppe*&@P$wnKE3+!)p_;STX)VXR3Q81+0Y6=IY*vBOJDdx*J>5L`ApQioU;HfU!Z# zT(KDF6d1d7EH>3trEL{s=a4WAO@-RGEqmJ*dhhhz?pyYDEh@|2eR*-8zE#tF0|iPS z5|Pm+{u-ZTVuvt)#mxNGidUUifZ!O`$P z;!9XZARzn@?%VhHIsQ9>0DMi7-y@$0pIfeQ5o>>kfkcA{}&=0ZQGnF@>i@)PF+2S>pfy_w1;;wj~=F5O0>FIBce^BXv= zVEx=1H{Q4(Y+nwxFGzQix0CmRE5ZGF_kMO%OB`D zWXn_(&8B3jDoch%)$GPKW1d3+nUZfBHjQ~n#^q*Sk_`+ayKf=8|IIjVkK8$b`~3a( z*O%L0zu$gnx&6==(my2sF8O6(rTyfr{nu8`ao~}Sgqr4GUby@(!LIva*H>cKx)OjX zzJ1t!IKchH$sP7tf8w$M{KPS`yNEl{Z(@%0k_#?Era25lbR`<$zsJpS9Qnj!g*$=w zfq5(DL@0CWcu`9`6i|JXsZiDFuwDu96!gDrYySq`zqb&mo43vn-E`&ytt;-pM66H@%{ZzPynyJ2Cr zAW%poney}*X$=!pskhPWRWY%_v5qiG@`#@ z#TqJFd7-f=a>Bkf8?n_q|rn+|;#+1XkIcFu4`A18DdZ8l-gBOfoc>2gHrCE(Y5413KLyFr}K z%ko(EwX9oy!ze)_q_?cH!oQFM|$51&7go*~m zell&%Sx3#lp+rf@T?3rvB?uoR(~EON1t+3EqwW$^em`YRe$-pWDM1on+Czz-X(0&_ zLrg4TB*_PuGn@QP+j0W9v3Wv<;qFe`r^RWYv)1RksV3#a+LAv;S#O3UO zg$ZlblM{1}5guzAImb*Fe34P~wpGt%Fhnn~o;4@_CI17yyejO?mJ@OIPcG&ho6nsS zF}i)h6@;8);|e+F4F3(>pA%QYnQ{)c-uKbLw~<$g!d-!i;5i5a!R`Oo2bOaLfMS+nDEMzd0wjn-j@ixr|`7|AJ`Q-MiYy-ERpqGTs%LROfH=!Gj6ixiES zq_`TVe}HrFBjcJ)&5l9MPV*QHD={3`^Z!hiz5{qHkkEw7@zjK@;Oz~UAKkgMLg!S# ztnZkGjWqNt2SCwg$RjtYt)J_=(Kj!Bb{a|?j)KcO`{o0G?R9A`c_X>F^I!e@?$JVh zc2lAb7|D*kj5A(ZE6fOLg zx;4Vr)`1(@)p6Z3f9WgVj@4lOeD|$GHxDh=FK>BuIru6NFAUu|cl+Eu+ZTJj-2P=l z{~CE-wm7u#_KJVs ztgTSra{XJgYF^yGc;MdQrGo%4pSxklFoV8h+`3sC+EB7Fxl?fCa7`=9HJ&nGkjwr# zjMG=JX@llPD;Q5o0VCEbDPS}PkzlFT+y$$B9~UZDGpch{c8*W(YDuV;ISZ`Wk~jrZ z!>8`>uK=S|4f2($L5UY!nqu0jJb#hT5e+>ilsClR)gpfb^V2bG*jZI>0fok-cM*U& z2u{E)095_x`C5)glRhovtbZi(BxlVDB?k((Ul;@fukt0z?Su*(=tP1)2O4pO=%!xJ zU3g|%HSvTz$^L=>$ygZZDs;m7>(A=9&QE;yt-P;&wZ8ef`!|m~#MkhUaE>ity#Azr%>vN6 zg2BZ{fiPKcby+^=x?m&FQF=_VorE5}vc8$vE_2kSf_U|(95a2yspcVIGF*y3%2g!{ z&uA7KjGCWFhUr1*ovJPCNJS0lpJ7O8hX%}r%X?j&o4zrj4>Z2KR(3ssQN8dpQ<_>rp8Oz`zH3Q6g1OInINa38I~8t}-?( z@7gE=1WH8!C?C>8Fc%n$!|>AmFiX}pmqiaCRe4WV@K}L<1&s+S3Os;9hVEdbACE1K z-47j{?R^jczqX*R1UgpS9atfF!>AA(c#Yd|-neI`CR+h`#(Be4*RMFoxCRFh1_I3z zr2Yi@Y^g}SkK*SxXg~wsF^5>)gP~(iPEo0z z95+iJY+~S`ir%b#P_w?ksuvOGrkG6O89Qj5e+!yT${zFta{P>S$~0x(P?71W=MZA3 zpx0PVBgDjmmC7fIMNDf;<%Yr1y zKxM72S+Lv?n=z_jS?{K_u07kH|0fmZHf2dT>@CM9sM(F9~48+$95QvY^^UE1)yxmv{ z-pC(NA_SDcA3{zniMSAQV@bpVP*E?wU|#^ZUF|)-X0ZtFLVc6|Zf)1!uk6*|hYqv% zs>AnAebCC3lp);;)EMm2yMcGznX<6qmJV83N36JcpK9o1UuQX=0FH`(y@kE zxRb3#4?x9Mi?FNchP+!{4EH=ygBQ2Ue7L&3Gz4$dU0e{Vh;ul4a1BuBb{#yV= z!%WepQ}m(~V-1Q4F2$^tIfWL`=tJkDT!IEXFUNh&^PKh9Ho~<% z<~)rq#Lf>1iE;j_I-4bIf3_&gwt^Vg3Sy!rv|Zz@WjsUHjy*Or z<3P-~3Y1Fr5-E;YDM7TtPgPc_)IRc9b$RQ{AhWF*Q8iNA2j0>R4_o@so_p;{A~w)o z+4r7%&$;KEx%Zs!oE z!$w%3k-(+7j3r_L+7huc=qwVfw~=5IEt_mZN5m$$1pYP`;RUx~htV#y2o4w>i_W0u zIS~qC!x@*PtQuF7SxI@08*7fKxU7h$WjQN@gd-f!h{8-hmlg}m;CqRN_D0_{g?HYC z`5F>Y1Pds_Tm-j8R$xxRs=xxp?NXLq%1Wq3TaY)LF=|japOi#{O-ia^O#>-q3l2q< z16m-Q1%^!#6U0q}lfWUvvJg+_#c+@__*jgr#A1dc7RzLXe45a%SnO&(o^EnHvDm&3 zjm17gJLC&q&QFX-FDjy}M5p7KXh>A%)od<0ofO2tl$=e0MJ0MBuBa!cei&F#0%y{3 zWj^{rJTWiIflxM;jUHVPrG@DES*z>3d_N;D}Y6Itm>a&9b^Bd2*ZwL&(GD}T{W(&?r5)Z?R96z6Rtz{nX(B6D6>JhAUJcp(+jI>Xbu$_ z0To%5TpVp0-fYq#H2W`vq99k~iWaqbQwV8DvuKjcHcJtrAaZ1ACl+O?$VnW-YS+j}-`Ubbyt4 zu!5H*u!EPcYRuOxG5ahrDW~bh-niK$i@e6CTzl&#we+g*`!({Z7E^9-lm|dl+uq!y zVD$j$`;ZZu?LPVy`y0+Yy&^ApU!%jeU+Um<(|A)4+`_*(q%qSdXbBg*VkW092AXLT zAem6;%S+<*9E7zf1ZV;UAWGGwl&1(_U`G(kZuo$p-LQ*NJ|oI;RfN2vF-(%uu*P#a zQ4$)#Ok%}gbJ?6sPLl3~bd<$hI-U?g2bYVh9~*YEF$U=&8&+9V@@Z8j7bBB2FgU`b z+YL^Lt8rL>d{apL1k5J46Gq?(nJZlo?@Op27+5+}Z}BdjeRin78vN6t z(Xy*#t#Hpej#Rm-uyLl=@$Iss#Fd0Cr$=`lu5xCXDl7p^1QW2pP>?PJL0U7DmIlGx3BI4y!e6hFcg zKMukTnH6vmORXAC?cWI-N!!gHtYHN&G8z_G0%QU=|4q2jDGdV}HL(F9z@Q<5KcH(D zL8uzq0SYy-s-fe}_Ljc^k&OilwPvN(Y%sS8mL5QgD-iPn(%954?(EqyeI_!dzi&bY z<+L5-4)R#w1=}W^9BC9eEk&zf*DMPV%PL75+Dv=5FzB(o$s^7BCT{ba@c)t-MCvY% z4I%k^7`@hi``7)K`d-=JhLP$qzXi~n5gZyWSmExQ@o)=YL)Y04(KY-bTE1%1L1)-N zK_(oQ$pIPo()0g7Gw`fT!ok4r8+g3n+Xp`+kH-VUG86#rq>{*HGNPoCMF0tS_LDio zHF05b@^pA+;)9bvnl>y-E}c|ipgdbrQQ)x!Z2B>&18A`#6F3Umhk zO%>zB9!@DFBAWn1@kKsFc93oi{>lW!mA~8CD>F}QJ#|l8nIqYGqkQ8|vBW)fdg`9; zEt|7+^5(V5;H{t4eO)E~uWfy$lR%L~Uyt8?zuNZLH~44Y$Yb9~%{N*OZL`Sh2LP%t z<>M9QlOKTSI$j^BjIXP@Z%}s)0y0#7y7tlPN41V&-7~E7!vIE={_;;&V&&Kqw||@A z?UT6P<9{%6Z=^cC(O>HsEuSleD#LY;w<45p0HEAW>#YM|srB%73uwN!LFWOLR&zFb;{o4%d1}px72dR6h4c|t* z=0CRTtZ=L1SI>NXUm^_LUE~V=UVS`vuGV>8Z#}=w64uLqvZ$+f+d@>mR7T)1|Ijix zF@!!JvWJG)&yRM5IOY+HfqumGg#4CAewNUKWF^r=1lUB8s)1I=VX_95OKLxtitHg0 zDV2Lr5pYRuW+*}n2wp-aOd~MJKvhX&M;?W(L8cKQ-vU}88v=nw6cB%9(oGQ#i6VGi zc-M-v?7ZWyT&`Tz`R=8Yb*Jl7m+tJdr=SGAn z2nJRrV2cL3e!!Wy!$h6(mFR>b0*NRQJvjDYjNd5y=2aD*pOBD5nJcxgp1ZcTI} zMAAeekeDUxL1xLsB@-bgU9nimtoRG8#kGW$fIF$l!(>j*@ho8&1L9NLa}9&7|967Z zzC$1+US^rRDqChs3y?%P%%Y93FeFVEL{vZgrXLA`3n2>dGP=|RZooJi_)LCUd{Qqzt{RPI z?Wo8E@A!nY0=5QHuImM60E1A&xR_!&&($bj(K%?LKfS`P-lXtxy>9mOeL}l|*VpT| zMUqCcnKXD%D=P!4lpK?G^#iljCBCxnop`!BAnrgvvX6CY2V|=cDYHYUZo78N zbK7<&zuK$XA2MmL=UJx?x$obE?%@Jx8{mw?7@tW9AL1YJM4t+vQw0u-`bp@%~9fQ RXLfsQg5a#btN3YX^$$7`xW51Z literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/iniconfig/__pycache__/exceptions.cpython-312.pyc b/.venv/Lib/site-packages/iniconfig/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3b78cb0fc107bdc2e47526a5bcb3899f1d493ad GIT binary patch literal 1282 zcmZ8gL2uMX6rQoiUhig?P_k^dDydd*a;v8IB&r%hLJ(*hks^U>G&1%iIP0~yGu|j! z?I99DO(c-uP$dXeaH^+xh zDC7{FKituqZ!kjtN~3qQu7dZ@g0hJ`Agkcmru^)v3%)(FCW~b=n%+%z?3)S=B(5C&+3Eb}0 zuH4Uq_h!KU6{=y#!XC1eD^L|vJ!KsHo(dXCHR;n*%~(hC*wfeY7V)xPW{p_dI$Fk- zK8MU)lACq7U{jn&yp>Cz{aM8PdO{jbe6#2A!}xll2Y$%HC{gQT!6k=klQcnjc<(YO zzu`?3_rVR)Mt1>kgQhpzN)5{XrkF*JTWNsLqDdJ z8*rOvrH})J2qIW8f5e?+DRl!!2ug)4vjS)JQ#3hhUlEK8d(Nrb=a{I)QNy0|Jyx0J z(IRtWVNW|Eo|wH*SrV0LNKmt99Jj`J=u^nTKe+J>Ek-nR#kR=^{qQY*b z8^BBQQJu56$-{(HqmY>jFM~5h%#7Tf(5W*@lN_b>$ZG~t&r|wK(+T#GK}rw(o3Lhb zLj0smUD#hGmjwZ((C{F5(}sUY8|E1qDurB4@fC=Vw^1iod`^m;eCatUb+SXZetb`e z_W6fA*2dG0(hF6n4r-cjLhVo!m;XYd$14p#T;OshTn;(OPW84bz4a&RW=PpK#=9MT o&(A=p9D^7J@m9qc@1oPYsPalZzDrKM)AM-z-WTr?eA4Ux0d%S>=Kufz literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/iniconfig/_parse.py b/.venv/Lib/site-packages/iniconfig/_parse.py new file mode 100644 index 0000000..2d03437 --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig/_parse.py @@ -0,0 +1,82 @@ +from __future__ import annotations +from .exceptions import ParseError + +from typing import NamedTuple + + +COMMENTCHARS = "#;" + + +class _ParsedLine(NamedTuple): + lineno: int + section: str | None + name: str | None + value: str | None + + +def parse_lines(path: str, line_iter: list[str]) -> list[_ParsedLine]: + result: list[_ParsedLine] = [] + section = None + for lineno, line in enumerate(line_iter): + name, data = _parseline(path, line, lineno) + # new value + if name is not None and data is not None: + result.append(_ParsedLine(lineno, section, name, data)) + # new section + elif name is not None and data is None: + if not name: + raise ParseError(path, lineno, "empty section name") + section = name + result.append(_ParsedLine(lineno, section, None, None)) + # continuation + elif name is None and data is not None: + if not result: + raise ParseError(path, lineno, "unexpected value continuation") + last = result.pop() + if last.name is None: + raise ParseError(path, lineno, "unexpected value continuation") + + if last.value: + last = last._replace(value=f"{last.value}\n{data}") + else: + last = last._replace(value=data) + result.append(last) + return result + + +def _parseline(path: str, line: str, lineno: int) -> tuple[str | None, str | None]: + # blank lines + if iscommentline(line): + line = "" + else: + line = line.rstrip() + if not line: + return None, None + # section + if line[0] == "[": + realline = line + for c in COMMENTCHARS: + line = line.split(c)[0].rstrip() + if line[-1] == "]": + return line[1:-1], None + return None, realline.strip() + # value + elif not line[0].isspace(): + try: + name, value = line.split("=", 1) + if ":" in name: + raise ValueError() + except ValueError: + try: + name, value = line.split(":", 1) + except ValueError: + raise ParseError(path, lineno, "unexpected line: %r" % line) + return name.strip(), value.strip() + # continuation + else: + return None, line.strip() + + +def iscommentline(line: str) -> bool: + c = line.lstrip()[:1] + return c in COMMENTCHARS diff --git a/.venv/Lib/site-packages/iniconfig/_version.py b/.venv/Lib/site-packages/iniconfig/_version.py new file mode 100644 index 0000000..e058e2c --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig/_version.py @@ -0,0 +1,21 @@ +# file generated by setuptools-scm +# don't change, don't track in version control + +__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"] + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple + from typing import Union + + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '2.1.0' +__version_tuple__ = version_tuple = (2, 1, 0) diff --git a/.venv/Lib/site-packages/iniconfig/exceptions.py b/.venv/Lib/site-packages/iniconfig/exceptions.py new file mode 100644 index 0000000..8c4dc9a --- /dev/null +++ b/.venv/Lib/site-packages/iniconfig/exceptions.py @@ -0,0 +1,20 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Final + + +class ParseError(Exception): + path: Final[str] + lineno: Final[int] + msg: Final[str] + + def __init__(self, path: str, lineno: int, msg: str) -> None: + super().__init__(path, lineno, msg) + self.path = path + self.lineno = lineno + self.msg = msg + + def __str__(self) -> str: + return f"{self.path}:{self.lineno + 1}: {self.msg}" diff --git a/.venv/Lib/site-packages/iniconfig/py.typed b/.venv/Lib/site-packages/iniconfig/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt new file mode 100644 index 0000000..7b190ca --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2011 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/METADATA b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/METADATA new file mode 100644 index 0000000..ddf5464 --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/METADATA @@ -0,0 +1,60 @@ +Metadata-Version: 2.1 +Name: itsdangerous +Version: 2.2.0 +Summary: Safely pass data to untrusted environments and back. +Maintainer-email: Pallets +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://itsdangerous.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/itsdangerous/ + +# ItsDangerous + +... so better sign this + +Various helpers to pass data to untrusted environments and to get it +back safe and sound. Data is cryptographically signed to ensure that a +token has not been tampered with. + +It's possible to customize how data is serialized. Data is compressed as +needed. A timestamp can be added and verified automatically while +loading a token. + + +## A Simple Example + +Here's how you could generate a token for transmitting a user's id and +name between web requests. + +```python +from itsdangerous import URLSafeSerializer +auth_s = URLSafeSerializer("secret key", "auth") +token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) + +print(token) +# eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg + +data = auth_s.loads(token) +print(data["name"]) +# itsdangerous +``` + + +## Donate + +The Pallets organization develops and supports ItsDangerous and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +[please donate today][]. + +[please donate today]: https://palletsprojects.com/donate + diff --git a/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/RECORD b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/RECORD new file mode 100644 index 0000000..1394876 --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/RECORD @@ -0,0 +1,22 @@ +itsdangerous-2.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +itsdangerous-2.2.0.dist-info/LICENSE.txt,sha256=Y68JiRtr6K0aQlLtQ68PTvun_JSOIoNnvtfzxa4LCdc,1475 +itsdangerous-2.2.0.dist-info/METADATA,sha256=0rk0-1ZwihuU5DnwJVwPWoEI4yWOyCexih3JyZHblhE,1924 +itsdangerous-2.2.0.dist-info/RECORD,, +itsdangerous-2.2.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +itsdangerous/__init__.py,sha256=4SK75sCe29xbRgQE1ZQtMHnKUuZYAf3bSpZOrff1IAY,1427 +itsdangerous/__pycache__/__init__.cpython-312.pyc,, +itsdangerous/__pycache__/_json.cpython-312.pyc,, +itsdangerous/__pycache__/encoding.cpython-312.pyc,, +itsdangerous/__pycache__/exc.cpython-312.pyc,, +itsdangerous/__pycache__/serializer.cpython-312.pyc,, +itsdangerous/__pycache__/signer.cpython-312.pyc,, +itsdangerous/__pycache__/timed.cpython-312.pyc,, +itsdangerous/__pycache__/url_safe.cpython-312.pyc,, +itsdangerous/_json.py,sha256=wPQGmge2yZ9328EHKF6gadGeyGYCJQKxtU-iLKE6UnA,473 +itsdangerous/encoding.py,sha256=wwTz5q_3zLcaAdunk6_vSoStwGqYWe307Zl_U87aRFM,1409 +itsdangerous/exc.py,sha256=Rr3exo0MRFEcPZltwecyK16VV1bE2K9_F1-d-ljcUn4,3201 +itsdangerous/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +itsdangerous/serializer.py,sha256=PmdwADLqkSyQLZ0jOKAgDsAW4k_H0TlA71Ei3z0C5aI,15601 +itsdangerous/signer.py,sha256=YO0CV7NBvHA6j549REHJFUjUojw2pHqwcUpQnU7yNYQ,9647 +itsdangerous/timed.py,sha256=6RvDMqNumGMxf0-HlpaZdN9PUQQmRvrQGplKhxuivUs,8083 +itsdangerous/url_safe.py,sha256=az4e5fXi_vs-YbWj8YZwn4wiVKfeD--GEKRT5Ueu4P4,2505 diff --git a/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/WHEEL b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/WHEEL new file mode 100644 index 0000000..3b5e64b --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/Lib/site-packages/itsdangerous/__init__.py b/.venv/Lib/site-packages/itsdangerous/__init__.py new file mode 100644 index 0000000..ea55256 --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous/__init__.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import typing as t + +from .encoding import base64_decode as base64_decode +from .encoding import base64_encode as base64_encode +from .encoding import want_bytes as want_bytes +from .exc import BadData as BadData +from .exc import BadHeader as BadHeader +from .exc import BadPayload as BadPayload +from .exc import BadSignature as BadSignature +from .exc import BadTimeSignature as BadTimeSignature +from .exc import SignatureExpired as SignatureExpired +from .serializer import Serializer as Serializer +from .signer import HMACAlgorithm as HMACAlgorithm +from .signer import NoneAlgorithm as NoneAlgorithm +from .signer import Signer as Signer +from .timed import TimedSerializer as TimedSerializer +from .timed import TimestampSigner as TimestampSigner +from .url_safe import URLSafeSerializer as URLSafeSerializer +from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer + + +def __getattr__(name: str) -> t.Any: + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " ItsDangerous 2.3. Use feature detection or" + " 'importlib.metadata.version(\"itsdangerous\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("itsdangerous") + + raise AttributeError(name) diff --git a/.venv/Lib/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd90e859a5e8816185e7aa8cff00b470df241928 GIT binary patch literal 1647 zcmZXU&ube;6vt=vW3^goC0YKRnq=*!b@mXoBxxaOD7bdq1X5$Fgu)_NMw+p__Wlqv zBReuSl+r_c>Y<0^+DmCk|C3&l=8&*KP)K_)^kA@0J@w64GIBaBPoMXl*{3)2-t6y| zWg-|qw{C6zm__J~bcQ2!3fl2M0FMztL<>-;xtb<96{OrWNs~;F3A1h%v^2>A>#nX? z2hO=U#ks%;^KM?T0c^Ua;yiG{EhsjDi*8YI0oZaa#YNzfTT*NV<*?#b6qkaruvYNw*r#GHP7!y~j66AG(_0*wZOxr-y=HG6_`n9hBM$qE)2Fb{Z_ zVnP`>COGT37sQx=R|K!_w<9dNOa`rh|G*FFIql53^Qhx9D&I5fl=(RD_f$;l?iWkT zOF=tkzSs<5+FFYvIx6TghFV*e;mNtzmBa-OJ7=rL9)5YhjyLGJbyEZJtq3i7Lc;;= zgwhCU$4>wrqkU+f?JEeePe_WO03B%RS35}Ur?%wUw+2ZIeJGL4Z`r31OAZa~Yg@)C zkJ_!msCUXle>%vhj)XOw-$z6B8_mhBB?Zshp^QU~J&(zz-}~voCbbtwGaAM_p!tz~Pw*8SwJD3c+`d-3Ub7!^YHv_gU3g#6 zmTW3JX7-{Vc48(1zgY{Zzyvy_Hpu77f-g83##wL{Av71zKea=dr>s5{tF;}_9U3IX z(ZxprKREa?=TiKC!J&>9MU9z z_7`jp65_H(wr&60S-#bP4Kv=T8Vjk-^$z0Tq-*gS8nVlI}}@7X)tUJWqc zZrp{PP-d^hTX6$UAlhl%_nQsw3wpH!Q@BleV|0rjc%C2m!t-jKo-AS;P7l(ABEAP{ z%*cG|V7^ALa)s{>{>aU}Hs)U&(|;P3AFS`Kqg2ZJTszT`b?Mdg%uxa5r%sTbH;+oF zGW)VT|GYf^vg|xBJHOOkly5#WZvJJ?Jehc5UVN6hcno2YlVJ<+lcc#|iOwk8+h{K< zYD-binM+L1+vv)x4C%;INp#Wcz`-Y)NDTF%0EfxGXR^&%PKBb6T1@sf%S$6a%}lm6 zD@r397z%!N-C0QrI#>6ZmF1F(G-FcE3VFF%RSM%$FuE-8I9y~lA+A1(Hg(??hP=p*%f*hkmY Sb9t!T?xWQcy`)`IfASy1KFZDj literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/itsdangerous/__pycache__/_json.cpython-312.pyc b/.venv/Lib/site-packages/itsdangerous/__pycache__/_json.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f923b2318b9557ca0caed85a733ef87b6adc1cb9 GIT binary patch literal 1201 zcmZ`&&1=*^6ragg<8HOMRmSQVmm6)Orwgrd|BWGxgCmOz+hrrqpE5@%*xR(tT! zix)lCqX!YCe~OnD1#MIi>cv~_9t2OmNp`m?IFL8Lc^@Oi@*%xT-UQnp9gg)_eX{6Eqe)v!}5-hC7rF``6-DLIQbv z?r!Dbh5MW&3F8joaXX?;UBr>oifOya99bjM5t4_AaF%PKWCFriIe^j!OJxKf?oI$$ zLvmoN_evXh0$4%oJ^LKgS#h=0^woo@*^CJlQ2J0pIvG}e&ac+BDes+iG3n_DEd0c0K6y6LGa(ecbvm~6ECJ_zqCJ7o)(Dk?;d*vk-EqQmsc~3z8XCeOv zS!BWsrJy8QWKgs?m*r4SI^2dwAC~^)r<^6Dx-Jfac#OV{j6QqxiQ?^62;TEHRP^AbPB zK&@Fxhgl@rocTlqVK|c(1WO1fGUftU#}g@@a&@JZlr4~UQ}Q9WCN*ihl_(EAp0B4? zW0~+pAyg+FhbtC%3B*4XWibapFD`+2jK10jKGyz*8^B)K2iPweq_T=Xt zg_D~j?u%0(Hf)yzWvJpBe4o0%2t((rIP)DCxzyrJwt4i+l;!ED<68U>xbP!Nj1JIY zK&cL4FyOZDN2JAkKP~uvKFWZHeg8q5G<%jI-=}e))I$AeL5s;+OtS&VN_>N|jPjaQ zOSuw?BXI`AD%vuxd^Hd5YS=u{)sa=&QE0~kw5#dn=}h-Sq5K{d4XHsw3vH+dlH;nO zsiDR{=iZW?B#ahQEVH-xF|h9?k=j@CQm*pjU{Et9j)K_HF~(oe@ohBv36*|YMSOY} Ifyg3%14QT?8~^|S literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc b/.venv/Lib/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8e5c6fc9ac37abcc30e01c8685a437e03af7b71 GIT binary patch literal 2701 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 EAJnSc^8f$< literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc b/.venv/Lib/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e88b424a29c989fe6b52588efa4d436f8ec6864d GIT binary patch literal 11306 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 OPTYC+^e3Dr{P0ijqN6ha literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/itsdangerous/_json.py b/.venv/Lib/site-packages/itsdangerous/_json.py new file mode 100644 index 0000000..fc23fea --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous/_json.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import json as _json +import typing as t + + +class _CompactJSON: + """Wrapper around json module that strips whitespace.""" + + @staticmethod + def loads(payload: str | bytes) -> t.Any: + return _json.loads(payload) + + @staticmethod + def dumps(obj: t.Any, **kwargs: t.Any) -> str: + kwargs.setdefault("ensure_ascii", False) + kwargs.setdefault("separators", (",", ":")) + return _json.dumps(obj, **kwargs) diff --git a/.venv/Lib/site-packages/itsdangerous/encoding.py b/.venv/Lib/site-packages/itsdangerous/encoding.py new file mode 100644 index 0000000..f5ca80f --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous/encoding.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import base64 +import string +import struct +import typing as t + +from .exc import BadData + + +def want_bytes( + s: str | bytes, encoding: str = "utf-8", errors: str = "strict" +) -> bytes: + if isinstance(s, str): + s = s.encode(encoding, errors) + + return s + + +def base64_encode(string: str | bytes) -> bytes: + """Base64 encode a string of bytes or text. The resulting bytes are + safe to use in URLs. + """ + string = want_bytes(string) + return base64.urlsafe_b64encode(string).rstrip(b"=") + + +def base64_decode(string: str | bytes) -> bytes: + """Base64 decode a URL-safe string of bytes or text. The result is + bytes. + """ + string = want_bytes(string, encoding="ascii", errors="ignore") + string += b"=" * (-len(string) % 4) + + try: + return base64.urlsafe_b64decode(string) + except (TypeError, ValueError) as e: + raise BadData("Invalid base64-encoded data") from e + + +# The alphabet used by base64.urlsafe_* +_base64_alphabet = f"{string.ascii_letters}{string.digits}-_=".encode("ascii") + +_int64_struct = struct.Struct(">Q") +_int_to_bytes = _int64_struct.pack +_bytes_to_int = t.cast("t.Callable[[bytes], tuple[int]]", _int64_struct.unpack) + + +def int_to_bytes(num: int) -> bytes: + return _int_to_bytes(num).lstrip(b"\x00") + + +def bytes_to_int(bytestr: bytes) -> int: + return _bytes_to_int(bytestr.rjust(8, b"\x00"))[0] diff --git a/.venv/Lib/site-packages/itsdangerous/exc.py b/.venv/Lib/site-packages/itsdangerous/exc.py new file mode 100644 index 0000000..a75adcd --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous/exc.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import typing as t +from datetime import datetime + + +class BadData(Exception): + """Raised if bad data of any sort was encountered. This is the base + for all exceptions that ItsDangerous defines. + + .. versionadded:: 0.15 + """ + + def __init__(self, message: str): + super().__init__(message) + self.message = message + + def __str__(self) -> str: + return self.message + + +class BadSignature(BadData): + """Raised if a signature does not match.""" + + def __init__(self, message: str, payload: t.Any | None = None): + super().__init__(message) + + #: The payload that failed the signature test. In some + #: situations you might still want to inspect this, even if + #: you know it was tampered with. + #: + #: .. versionadded:: 0.14 + self.payload: t.Any | None = payload + + +class BadTimeSignature(BadSignature): + """Raised if a time-based signature is invalid. This is a subclass + of :class:`BadSignature`. + """ + + def __init__( + self, + message: str, + payload: t.Any | None = None, + date_signed: datetime | None = None, + ): + super().__init__(message, payload) + + #: If the signature expired this exposes the date of when the + #: signature was created. This can be helpful in order to + #: tell the user how long a link has been gone stale. + #: + #: .. versionchanged:: 2.0 + #: The datetime value is timezone-aware rather than naive. + #: + #: .. versionadded:: 0.14 + self.date_signed = date_signed + + +class SignatureExpired(BadTimeSignature): + """Raised if a signature timestamp is older than ``max_age``. This + is a subclass of :exc:`BadTimeSignature`. + """ + + +class BadHeader(BadSignature): + """Raised if a signed header is invalid in some form. This only + happens for serializers that have a header that goes with the + signature. + + .. versionadded:: 0.24 + """ + + def __init__( + self, + message: str, + payload: t.Any | None = None, + header: t.Any | None = None, + original_error: Exception | None = None, + ): + super().__init__(message, payload) + + #: If the header is actually available but just malformed it + #: might be stored here. + self.header: t.Any | None = header + + #: If available, the error that indicates why the payload was + #: not valid. This might be ``None``. + self.original_error: Exception | None = original_error + + +class BadPayload(BadData): + """Raised if a payload is invalid. This could happen if the payload + is loaded despite an invalid signature, or if there is a mismatch + between the serializer and deserializer. The original exception + that occurred during loading is stored on as :attr:`original_error`. + + .. versionadded:: 0.15 + """ + + def __init__(self, message: str, original_error: Exception | None = None): + super().__init__(message) + + #: If available, the error that indicates why the payload was + #: not valid. This might be ``None``. + self.original_error: Exception | None = original_error diff --git a/.venv/Lib/site-packages/itsdangerous/py.typed b/.venv/Lib/site-packages/itsdangerous/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/itsdangerous/serializer.py b/.venv/Lib/site-packages/itsdangerous/serializer.py new file mode 100644 index 0000000..5ddf387 --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous/serializer.py @@ -0,0 +1,406 @@ +from __future__ import annotations + +import collections.abc as cabc +import json +import typing as t + +from .encoding import want_bytes +from .exc import BadPayload +from .exc import BadSignature +from .signer import _make_keys_list +from .signer import Signer + +if t.TYPE_CHECKING: + import typing_extensions as te + + # This should be either be str or bytes. To avoid having to specify the + # bound type, it falls back to a union if structural matching fails. + _TSerialized = te.TypeVar( + "_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes] + ) +else: + # Still available at runtime on Python < 3.13, but without the default. + _TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes]) + + +class _PDataSerializer(t.Protocol[_TSerialized]): + def loads(self, payload: _TSerialized, /) -> t.Any: ... + # A signature with additional arguments is not handled correctly by type + # checkers right now, so an overload is used below for serializers that + # don't match this strict protocol. + def dumps(self, obj: t.Any, /) -> _TSerialized: ... + + +# Use TypeIs once it's available in typing_extensions or 3.13. +def is_text_serializer( + serializer: _PDataSerializer[t.Any], +) -> te.TypeGuard[_PDataSerializer[str]]: + """Checks whether a serializer generates text or binary.""" + return isinstance(serializer.dumps({}), str) + + +class Serializer(t.Generic[_TSerialized]): + """A serializer wraps a :class:`~itsdangerous.signer.Signer` to + enable serializing and securely signing data other than bytes. It + can unsign to verify that the data hasn't been changed. + + The serializer provides :meth:`dumps` and :meth:`loads`, similar to + :mod:`json`, and by default uses :mod:`json` internally to serialize + the data to bytes. + + The secret key should be a random string of ``bytes`` and should not + be saved to code or version control. Different salts should be used + to distinguish signing in different contexts. See :doc:`/concepts` + for information about the security of the secret key and salt. + + :param secret_key: The secret key to sign and verify with. Can be a + list of keys, oldest to newest, to support key rotation. + :param salt: Extra key to combine with ``secret_key`` to distinguish + signatures in different contexts. + :param serializer: An object that provides ``dumps`` and ``loads`` + methods for serializing data to a string. Defaults to + :attr:`default_serializer`, which defaults to :mod:`json`. + :param serializer_kwargs: Keyword arguments to pass when calling + ``serializer.dumps``. + :param signer: A ``Signer`` class to instantiate when signing data. + Defaults to :attr:`default_signer`, which defaults to + :class:`~itsdangerous.signer.Signer`. + :param signer_kwargs: Keyword arguments to pass when instantiating + the ``Signer`` class. + :param fallback_signers: List of signer parameters to try when + unsigning with the default signer fails. Each item can be a dict + of ``signer_kwargs``, a ``Signer`` class, or a tuple of + ``(signer, signer_kwargs)``. Defaults to + :attr:`default_fallback_signers`. + + .. versionchanged:: 2.0 + Added support for key rotation by passing a list to + ``secret_key``. + + .. versionchanged:: 2.0 + Removed the default SHA-512 fallback signer from + ``default_fallback_signers``. + + .. versionchanged:: 1.1 + Added support for ``fallback_signers`` and configured a default + SHA-512 fallback. This fallback is for users who used the yanked + 1.0.0 release which defaulted to SHA-512. + + .. versionchanged:: 0.14 + The ``signer`` and ``signer_kwargs`` parameters were added to + the constructor. + """ + + #: The default serialization module to use to serialize data to a + #: string internally. The default is :mod:`json`, but can be changed + #: to any object that provides ``dumps`` and ``loads`` methods. + default_serializer: _PDataSerializer[t.Any] = json + + #: The default ``Signer`` class to instantiate when signing data. + #: The default is :class:`itsdangerous.signer.Signer`. + default_signer: type[Signer] = Signer + + #: The default fallback signers to try when unsigning fails. + default_fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = [] + + # Serializer[str] if no data serializer is provided, or if it returns str. + @t.overload + def __init__( + self: Serializer[str], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: None | _PDataSerializer[str] = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer positional argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer keyword argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a positional argument. If the strict signature of + # _PDataSerializer doesn't match, fall back to a union, requiring the user + # to specify the type. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a keyword argument. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: t.Any | None = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): + #: The list of secret keys to try for verifying signatures, from + #: oldest to newest. The newest (last) key is used for signing. + #: + #: This allows a key rotation system to keep a list of allowed + #: keys and remove expired ones. + self.secret_keys: list[bytes] = _make_keys_list(secret_key) + + if salt is not None: + salt = want_bytes(salt) + # if salt is None then the signer's default is used + + self.salt = salt + + if serializer is None: + serializer = self.default_serializer + + self.serializer: _PDataSerializer[_TSerialized] = serializer + self.is_text_serializer: bool = is_text_serializer(serializer) + + if signer is None: + signer = self.default_signer + + self.signer: type[Signer] = signer + self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {} + + if fallback_signers is None: + fallback_signers = list(self.default_fallback_signers) + + self.fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = fallback_signers + self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {} + + @property + def secret_key(self) -> bytes: + """The newest (last) entry in the :attr:`secret_keys` list. This + is for compatibility from before key rotation support was added. + """ + return self.secret_keys[-1] + + def load_payload( + self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None + ) -> t.Any: + """Loads the encoded object. This function raises + :class:`.BadPayload` if the payload is not valid. The + ``serializer`` parameter can be used to override the serializer + stored on the class. The encoded ``payload`` should always be + bytes. + """ + if serializer is None: + use_serializer = self.serializer + is_text = self.is_text_serializer + else: + use_serializer = serializer + is_text = is_text_serializer(serializer) + + try: + if is_text: + return use_serializer.loads(payload.decode("utf-8")) # type: ignore[arg-type] + + return use_serializer.loads(payload) # type: ignore[arg-type] + except Exception as e: + raise BadPayload( + "Could not load the payload because an exception" + " occurred on unserializing the data.", + original_error=e, + ) from e + + def dump_payload(self, obj: t.Any) -> bytes: + """Dumps the encoded object. The return value is always bytes. + If the internal serializer returns text, the value will be + encoded as UTF-8. + """ + return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs)) + + def make_signer(self, salt: str | bytes | None = None) -> Signer: + """Creates a new instance of the signer to be used. The default + implementation uses the :class:`.Signer` base class. + """ + if salt is None: + salt = self.salt + + return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs) + + def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]: + """Iterates over all signers to be tried for unsigning. Starts + with the configured signer, then constructs each signer + specified in ``fallback_signers``. + """ + if salt is None: + salt = self.salt + + yield self.make_signer(salt) + + for fallback in self.fallback_signers: + if isinstance(fallback, dict): + kwargs = fallback + fallback = self.signer + elif isinstance(fallback, tuple): + fallback, kwargs = fallback + else: + kwargs = self.signer_kwargs + + for secret_key in self.secret_keys: + yield fallback(secret_key, salt=salt, **kwargs) + + def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized: + """Returns a signed string serialized with the internal + serializer. The return value can be either a byte or unicode + string depending on the format of the internal serializer. + """ + payload = want_bytes(self.dump_payload(obj)) + rv = self.make_signer(salt).sign(payload) + + if self.is_text_serializer: + return rv.decode("utf-8") # type: ignore[return-value] + + return rv # type: ignore[return-value] + + def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None: + """Like :meth:`dumps` but dumps into a file. The file handle has + to be compatible with what the internal serializer expects. + """ + f.write(self.dumps(obj, salt)) + + def loads( + self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any + ) -> t.Any: + """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the + signature validation fails. + """ + s = want_bytes(s) + last_exception = None + + for signer in self.iter_unsigners(salt): + try: + return self.load_payload(signer.unsign(s)) + except BadSignature as err: + last_exception = err + + raise t.cast(BadSignature, last_exception) + + def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any: + """Like :meth:`loads` but loads from a file.""" + return self.loads(f.read(), salt) + + def loads_unsafe( + self, s: str | bytes, salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: + """Like :meth:`loads` but without verifying the signature. This + is potentially very dangerous to use depending on how your + serializer works. The return value is ``(signature_valid, + payload)`` instead of just the payload. The first item will be a + boolean that indicates if the signature is valid. This function + never fails. + + Use it for debugging only and if you know that your serializer + module is not exploitable (for example, do not use it with a + pickle serializer). + + .. versionadded:: 0.15 + """ + return self._loads_unsafe_impl(s, salt) + + def _loads_unsafe_impl( + self, + s: str | bytes, + salt: str | bytes | None, + load_kwargs: dict[str, t.Any] | None = None, + load_payload_kwargs: dict[str, t.Any] | None = None, + ) -> tuple[bool, t.Any]: + """Low level helper function to implement :meth:`loads_unsafe` + in serializer subclasses. + """ + if load_kwargs is None: + load_kwargs = {} + + try: + return True, self.loads(s, salt=salt, **load_kwargs) + except BadSignature as e: + if e.payload is None: + return False, None + + if load_payload_kwargs is None: + load_payload_kwargs = {} + + try: + return ( + False, + self.load_payload(e.payload, **load_payload_kwargs), + ) + except BadPayload: + return False, None + + def load_unsafe( + self, f: t.IO[t.Any], salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: + """Like :meth:`loads_unsafe` but loads from a file. + + .. versionadded:: 0.15 + """ + return self.loads_unsafe(f.read(), salt=salt) diff --git a/.venv/Lib/site-packages/itsdangerous/signer.py b/.venv/Lib/site-packages/itsdangerous/signer.py new file mode 100644 index 0000000..e324dc0 --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous/signer.py @@ -0,0 +1,266 @@ +from __future__ import annotations + +import collections.abc as cabc +import hashlib +import hmac +import typing as t + +from .encoding import _base64_alphabet +from .encoding import base64_decode +from .encoding import base64_encode +from .encoding import want_bytes +from .exc import BadSignature + + +class SigningAlgorithm: + """Subclasses must implement :meth:`get_signature` to provide + signature generation functionality. + """ + + def get_signature(self, key: bytes, value: bytes) -> bytes: + """Returns the signature for the given key and value.""" + raise NotImplementedError() + + def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool: + """Verifies the given signature matches the expected + signature. + """ + return hmac.compare_digest(sig, self.get_signature(key, value)) + + +class NoneAlgorithm(SigningAlgorithm): + """Provides an algorithm that does not perform any signing and + returns an empty signature. + """ + + def get_signature(self, key: bytes, value: bytes) -> bytes: + return b"" + + +def _lazy_sha1(string: bytes = b"") -> t.Any: + """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include + SHA-1, in which case the import and use as a default would fail before the + developer can configure something else. + """ + return hashlib.sha1(string) + + +class HMACAlgorithm(SigningAlgorithm): + """Provides signature generation using HMACs.""" + + #: The digest method to use with the MAC algorithm. This defaults to + #: SHA1, but can be changed to any other function in the hashlib + #: module. + default_digest_method: t.Any = staticmethod(_lazy_sha1) + + def __init__(self, digest_method: t.Any = None): + if digest_method is None: + digest_method = self.default_digest_method + + self.digest_method: t.Any = digest_method + + def get_signature(self, key: bytes, value: bytes) -> bytes: + mac = hmac.new(key, msg=value, digestmod=self.digest_method) + return mac.digest() + + +def _make_keys_list( + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], +) -> list[bytes]: + if isinstance(secret_key, (str, bytes)): + return [want_bytes(secret_key)] + + return [want_bytes(s) for s in secret_key] # pyright: ignore + + +class Signer: + """A signer securely signs bytes, then unsigns them to verify that + the value hasn't been changed. + + The secret key should be a random string of ``bytes`` and should not + be saved to code or version control. Different salts should be used + to distinguish signing in different contexts. See :doc:`/concepts` + for information about the security of the secret key and salt. + + :param secret_key: The secret key to sign and verify with. Can be a + list of keys, oldest to newest, to support key rotation. + :param salt: Extra key to combine with ``secret_key`` to distinguish + signatures in different contexts. + :param sep: Separator between the signature and value. + :param key_derivation: How to derive the signing key from the secret + key and salt. Possible values are ``concat``, ``django-concat``, + or ``hmac``. Defaults to :attr:`default_key_derivation`, which + defaults to ``django-concat``. + :param digest_method: Hash function to use when generating the HMAC + signature. Defaults to :attr:`default_digest_method`, which + defaults to :func:`hashlib.sha1`. Note that the security of the + hash alone doesn't apply when used intermediately in HMAC. + :param algorithm: A :class:`SigningAlgorithm` instance to use + instead of building a default :class:`HMACAlgorithm` with the + ``digest_method``. + + .. versionchanged:: 2.0 + Added support for key rotation by passing a list to + ``secret_key``. + + .. versionchanged:: 0.18 + ``algorithm`` was added as an argument to the class constructor. + + .. versionchanged:: 0.14 + ``key_derivation`` and ``digest_method`` were added as arguments + to the class constructor. + """ + + #: The default digest method to use for the signer. The default is + #: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or + #: compatible object. Note that the security of the hash alone + #: doesn't apply when used intermediately in HMAC. + #: + #: .. versionadded:: 0.14 + default_digest_method: t.Any = staticmethod(_lazy_sha1) + + #: The default scheme to use to derive the signing key from the + #: secret key and salt. The default is ``django-concat``. Possible + #: values are ``concat``, ``django-concat``, and ``hmac``. + #: + #: .. versionadded:: 0.14 + default_key_derivation: str = "django-concat" + + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous.Signer", + sep: str | bytes = b".", + key_derivation: str | None = None, + digest_method: t.Any | None = None, + algorithm: SigningAlgorithm | None = None, + ): + #: The list of secret keys to try for verifying signatures, from + #: oldest to newest. The newest (last) key is used for signing. + #: + #: This allows a key rotation system to keep a list of allowed + #: keys and remove expired ones. + self.secret_keys: list[bytes] = _make_keys_list(secret_key) + self.sep: bytes = want_bytes(sep) + + if self.sep in _base64_alphabet: + raise ValueError( + "The given separator cannot be used because it may be" + " contained in the signature itself. ASCII letters," + " digits, and '-_=' must not be used." + ) + + if salt is not None: + salt = want_bytes(salt) + else: + salt = b"itsdangerous.Signer" + + self.salt = salt + + if key_derivation is None: + key_derivation = self.default_key_derivation + + self.key_derivation: str = key_derivation + + if digest_method is None: + digest_method = self.default_digest_method + + self.digest_method: t.Any = digest_method + + if algorithm is None: + algorithm = HMACAlgorithm(self.digest_method) + + self.algorithm: SigningAlgorithm = algorithm + + @property + def secret_key(self) -> bytes: + """The newest (last) entry in the :attr:`secret_keys` list. This + is for compatibility from before key rotation support was added. + """ + return self.secret_keys[-1] + + def derive_key(self, secret_key: str | bytes | None = None) -> bytes: + """This method is called to derive the key. The default key + derivation choices can be overridden here. Key derivation is not + intended to be used as a security method to make a complex key + out of a short password. Instead you should use large random + secret keys. + + :param secret_key: A specific secret key to derive from. + Defaults to the last item in :attr:`secret_keys`. + + .. versionchanged:: 2.0 + Added the ``secret_key`` parameter. + """ + if secret_key is None: + secret_key = self.secret_keys[-1] + else: + secret_key = want_bytes(secret_key) + + if self.key_derivation == "concat": + return t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) + elif self.key_derivation == "django-concat": + return t.cast( + bytes, self.digest_method(self.salt + b"signer" + secret_key).digest() + ) + elif self.key_derivation == "hmac": + mac = hmac.new(secret_key, digestmod=self.digest_method) + mac.update(self.salt) + return mac.digest() + elif self.key_derivation == "none": + return secret_key + else: + raise TypeError("Unknown key derivation method") + + def get_signature(self, value: str | bytes) -> bytes: + """Returns the signature for the given value.""" + value = want_bytes(value) + key = self.derive_key() + sig = self.algorithm.get_signature(key, value) + return base64_encode(sig) + + def sign(self, value: str | bytes) -> bytes: + """Signs the given string.""" + value = want_bytes(value) + return value + self.sep + self.get_signature(value) + + def verify_signature(self, value: str | bytes, sig: str | bytes) -> bool: + """Verifies the signature for the given value.""" + try: + sig = base64_decode(sig) + except Exception: + return False + + value = want_bytes(value) + + for secret_key in reversed(self.secret_keys): + key = self.derive_key(secret_key) + + if self.algorithm.verify_signature(key, value, sig): + return True + + return False + + def unsign(self, signed_value: str | bytes) -> bytes: + """Unsigns the given string.""" + signed_value = want_bytes(signed_value) + + if self.sep not in signed_value: + raise BadSignature(f"No {self.sep!r} found in value") + + value, sig = signed_value.rsplit(self.sep, 1) + + if self.verify_signature(value, sig): + return value + + raise BadSignature(f"Signature {sig!r} does not match", payload=value) + + def validate(self, signed_value: str | bytes) -> bool: + """Only validates the given signed value. Returns ``True`` if + the signature exists and is valid. + """ + try: + self.unsign(signed_value) + return True + except BadSignature: + return False diff --git a/.venv/Lib/site-packages/itsdangerous/timed.py b/.venv/Lib/site-packages/itsdangerous/timed.py new file mode 100644 index 0000000..7384375 --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous/timed.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +import collections.abc as cabc +import time +import typing as t +from datetime import datetime +from datetime import timezone + +from .encoding import base64_decode +from .encoding import base64_encode +from .encoding import bytes_to_int +from .encoding import int_to_bytes +from .encoding import want_bytes +from .exc import BadSignature +from .exc import BadTimeSignature +from .exc import SignatureExpired +from .serializer import _TSerialized +from .serializer import Serializer +from .signer import Signer + + +class TimestampSigner(Signer): + """Works like the regular :class:`.Signer` but also records the time + of the signing and can be used to expire signatures. The + :meth:`unsign` method can raise :exc:`.SignatureExpired` if the + unsigning failed because the signature is expired. + """ + + def get_timestamp(self) -> int: + """Returns the current timestamp. The function must return an + integer. + """ + return int(time.time()) + + def timestamp_to_datetime(self, ts: int) -> datetime: + """Convert the timestamp from :meth:`get_timestamp` into an + aware :class`datetime.datetime` in UTC. + + .. versionchanged:: 2.0 + The timestamp is returned as a timezone-aware ``datetime`` + in UTC rather than a naive ``datetime`` assumed to be UTC. + """ + return datetime.fromtimestamp(ts, tz=timezone.utc) + + def sign(self, value: str | bytes) -> bytes: + """Signs the given string and also attaches time information.""" + value = want_bytes(value) + timestamp = base64_encode(int_to_bytes(self.get_timestamp())) + sep = want_bytes(self.sep) + value = value + sep + timestamp + return value + sep + self.get_signature(value) + + # Ignore overlapping signatures check, return_timestamp is the only + # parameter that affects the return type. + + @t.overload + def unsign( # type: ignore[overload-overlap] + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[False] = False, + ) -> bytes: ... + + @t.overload + def unsign( + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[True] = True, + ) -> tuple[bytes, datetime]: ... + + def unsign( + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: bool = False, + ) -> tuple[bytes, datetime] | bytes: + """Works like the regular :meth:`.Signer.unsign` but can also + validate the time. See the base docstring of the class for + the general behavior. If ``return_timestamp`` is ``True`` the + timestamp of the signature will be returned as an aware + :class:`datetime.datetime` object in UTC. + + .. versionchanged:: 2.0 + The timestamp is returned as a timezone-aware ``datetime`` + in UTC rather than a naive ``datetime`` assumed to be UTC. + """ + try: + result = super().unsign(signed_value) + sig_error = None + except BadSignature as e: + sig_error = e + result = e.payload or b"" + + sep = want_bytes(self.sep) + + # If there is no timestamp in the result there is something + # seriously wrong. In case there was a signature error, we raise + # that one directly, otherwise we have a weird situation in + # which we shouldn't have come except someone uses a time-based + # serializer on non-timestamp data, so catch that. + if sep not in result: + if sig_error: + raise sig_error + + raise BadTimeSignature("timestamp missing", payload=result) + + value, ts_bytes = result.rsplit(sep, 1) + ts_int: int | None = None + ts_dt: datetime | None = None + + try: + ts_int = bytes_to_int(base64_decode(ts_bytes)) + except Exception: + pass + + # Signature is *not* okay. Raise a proper error now that we have + # split the value and the timestamp. + if sig_error is not None: + if ts_int is not None: + try: + ts_dt = self.timestamp_to_datetime(ts_int) + except (ValueError, OSError, OverflowError) as exc: + # Windows raises OSError + # 32-bit raises OverflowError + raise BadTimeSignature( + "Malformed timestamp", payload=value + ) from exc + + raise BadTimeSignature(str(sig_error), payload=value, date_signed=ts_dt) + + # Signature was okay but the timestamp is actually not there or + # malformed. Should not happen, but we handle it anyway. + if ts_int is None: + raise BadTimeSignature("Malformed timestamp", payload=value) + + # Check timestamp is not older than max_age + if max_age is not None: + age = self.get_timestamp() - ts_int + + if age > max_age: + raise SignatureExpired( + f"Signature age {age} > {max_age} seconds", + payload=value, + date_signed=self.timestamp_to_datetime(ts_int), + ) + + if age < 0: + raise SignatureExpired( + f"Signature age {age} < 0 seconds", + payload=value, + date_signed=self.timestamp_to_datetime(ts_int), + ) + + if return_timestamp: + return value, self.timestamp_to_datetime(ts_int) + + return value + + def validate(self, signed_value: str | bytes, max_age: int | None = None) -> bool: + """Only validates the given signed value. Returns ``True`` if + the signature exists and is valid.""" + try: + self.unsign(signed_value, max_age=max_age) + return True + except BadSignature: + return False + + +class TimedSerializer(Serializer[_TSerialized]): + """Uses :class:`TimestampSigner` instead of the default + :class:`.Signer`. + """ + + default_signer: type[TimestampSigner] = TimestampSigner + + def iter_unsigners( + self, salt: str | bytes | None = None + ) -> cabc.Iterator[TimestampSigner]: + return t.cast("cabc.Iterator[TimestampSigner]", super().iter_unsigners(salt)) + + # TODO: Signature is incompatible because parameters were added + # before salt. + + def loads( # type: ignore[override] + self, + s: str | bytes, + max_age: int | None = None, + return_timestamp: bool = False, + salt: str | bytes | None = None, + ) -> t.Any: + """Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the + signature validation fails. If a ``max_age`` is provided it will + ensure the signature is not older than that time in seconds. In + case the signature is outdated, :exc:`.SignatureExpired` is + raised. All arguments are forwarded to the signer's + :meth:`~TimestampSigner.unsign` method. + """ + s = want_bytes(s) + last_exception = None + + for signer in self.iter_unsigners(salt): + try: + base64d, timestamp = signer.unsign( + s, max_age=max_age, return_timestamp=True + ) + payload = self.load_payload(base64d) + + if return_timestamp: + return payload, timestamp + + return payload + except SignatureExpired: + # The signature was unsigned successfully but was + # expired. Do not try the next signer. + raise + except BadSignature as err: + last_exception = err + + raise t.cast(BadSignature, last_exception) + + def loads_unsafe( # type: ignore[override] + self, + s: str | bytes, + max_age: int | None = None, + salt: str | bytes | None = None, + ) -> tuple[bool, t.Any]: + return self._loads_unsafe_impl(s, salt, load_kwargs={"max_age": max_age}) diff --git a/.venv/Lib/site-packages/itsdangerous/url_safe.py b/.venv/Lib/site-packages/itsdangerous/url_safe.py new file mode 100644 index 0000000..56a0793 --- /dev/null +++ b/.venv/Lib/site-packages/itsdangerous/url_safe.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import typing as t +import zlib + +from ._json import _CompactJSON +from .encoding import base64_decode +from .encoding import base64_encode +from .exc import BadPayload +from .serializer import _PDataSerializer +from .serializer import Serializer +from .timed import TimedSerializer + + +class URLSafeSerializerMixin(Serializer[str]): + """Mixed in with a regular serializer it will attempt to zlib + compress the string to make it shorter if necessary. It will also + base64 encode the string so that it can safely be placed in a URL. + """ + + default_serializer: _PDataSerializer[str] = _CompactJSON + + def load_payload( + self, + payload: bytes, + *args: t.Any, + serializer: t.Any | None = None, + **kwargs: t.Any, + ) -> t.Any: + decompress = False + + if payload.startswith(b"."): + payload = payload[1:] + decompress = True + + try: + json = base64_decode(payload) + except Exception as e: + raise BadPayload( + "Could not base64 decode the payload because of an exception", + original_error=e, + ) from e + + if decompress: + try: + json = zlib.decompress(json) + except Exception as e: + raise BadPayload( + "Could not zlib decompress the payload before decoding the payload", + original_error=e, + ) from e + + return super().load_payload(json, *args, **kwargs) + + def dump_payload(self, obj: t.Any) -> bytes: + json = super().dump_payload(obj) + is_compressed = False + compressed = zlib.compress(json) + + if len(compressed) < (len(json) - 1): + json = compressed + is_compressed = True + + base64d = base64_encode(json) + + if is_compressed: + base64d = b"." + base64d + + return base64d + + +class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]): + """Works like :class:`.Serializer` but dumps and loads into a URL + safe string consisting of the upper and lowercase character of the + alphabet as well as ``'_'``, ``'-'`` and ``'.'``. + """ + + +class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer[str]): + """Works like :class:`.TimedSerializer` but dumps and loads into a + URL safe string consisting of the upper and lowercase character of + the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. + """ diff --git a/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/INSTALLER b/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/METADATA b/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/METADATA new file mode 100644 index 0000000..ffef2ff --- /dev/null +++ b/.venv/Lib/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 %} + +{% 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/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/RECORD b/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/RECORD new file mode 100644 index 0000000..0ca35a7 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/WHEEL b/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/WHEEL new file mode 100644 index 0000000..23d2d7e --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/entry_points.txt b/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/entry_points.txt new file mode 100644 index 0000000..abc3eae --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt b/.venv/Lib/site-packages/jinja2-3.1.6.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..c37cae4 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2/__init__.py b/.venv/Lib/site-packages/jinja2/__init__.py new file mode 100644 index 0000000..1a423a3 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1313e3f53435c826aa76415393f391e2eceb19b6 GIT binary patch literal 1672 zcmZ9MTW=dh6vxML65H!be2a5WCP@p5s<pb7M3A5HXTnT0LuN9Dv%p!BwLA+vOJ*(4g>%t7nYTO-oFh5QxiB9U zNWpR**ddPP0&tNOEjz#^QnFkGE|aq55^#l7ESG^7$b#icSdD6=W_ba)PU@Dczzx!{ zTmx>BrsX>DB3ZQD0B(_%~2 zc@=n*Y+7Cmw<1ii<#pg~vTb<-c!%s*-UQwyyOy_r?~pr|G4Ne-7kHa|FxuJMee?5| z0l%U+P?%!PqBx|QVa6{5&hU3K(3)`^a2)p~!{;O26TFRIY8*>32z&;;94`3&H)p3< zjku;mtb0^r)sJH#wSump8Ho*J$&?BNhaTLAKNhiukcN%|qbr8YhiM0a6g*;Fx1B$s zKG0rfTs|0S<_Vu2Qm@CrSUL$pb}>@0+V`7UXDsqen*V-F@pv*ejFJEP-By8HDL+?= zNwcV9DTOpsr!A>bO^sJVpNKy9A*q>qU-h{TB9`XXQeRCaH&XM$9#)!LdCh(HEgY1e zI?e%A>{L*nnT3=Ndm`{Av2zr7`nI(?Ignega?rBsesw|_b9v?6-V+)!LX&0M;0C^tKS+=UA*VWHE>h3TN= zg^WtqzA}q#i<$Oys_aUhQN- zEE*{pDI2MPjB}xVdrB@Ct7@cXq;8~Pq-kW)NDE|~lQ#ti)m-;i^cF1i@73Xx&TA-c)w!Ti=ZL9mE#l4vTwr`I z#T6Wt>YPxepP#?P1BFjQs;)aPpgx$yN8(C!+5)aDW^4)Rwvlga!&lg-Tjp03xecN3rwO{3p!?|$Ldbmldu8#5qPz!F{s-RB B?PCA{ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..030a462d7a59a0a827e8998cb3d8805f9a058cf3 GIT binary patch literal 2153 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

=#T+qG?<1U4@P>wj+-f~|j`V9JI+tTsKO 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 Q2~ljA`R4Bh%GoskZ(>WMmH+?% literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/compiler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56d38cd5149003b2ef99a26cc9f891a41d39cc5b GIT binary patch literal 103911 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 J)uZx&k&ia_KED6} literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/debug.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/debug.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98b5698bbc91a588c1fce8496ac85a43f5dd6a00 GIT binary patch literal 6585 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 AVE_OC literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..589ef54b9efdb772494ac9e31e40a094c9e0668e GIT binary patch literal 1625 zcmah}&2Jk;6rc51{1H3;%!iYlZy`?HAPQ2ZENjb5*p83u1{ocGcZ)W^cJRU{x z{LsC2?_~g?-<^T?L?+-(jEfB*-yU_T^Hxj3kW`w=PP z;t=4d6m{_w;FuJ1aTxHlG%X+^^zvS?lb#0LI07-Cq5n`^9umU0-m`JJ?QfPqf5HFl(dLb(h^Qfr*Hc{$!ucm*=^R9=dU>4mWFGiCCu0w7`zDz#I#l=`dTzEnqIhK_~=|FoT-%E*Y_6*h+Ve{y>EaloZxgRhgO8iVmr! zTBJmYX2mDiROk*dt?Xn-zA~vIv+g|t6|rVaI@G&Rx{^l717&1odd$#Ec-S8>c^gco zYUoz#HFEa>%*U)G%u`a|dRH%*RPFZ(=E+sDTHdNdZIN=4OD%H3nri1 zRyC*=qj0ehpYg<#jpad;Fq2VtnQ_nPZZ!`MO;uww1b%eNX~It9+(#a7f6^4?${n$? zR%_hmsaN4-$c^X>GH2Xp5}v+Q->9rPd%M|&cGHn2UvXk%)njrWmN#qVTXpvkoCq`j z(ywW=-huefoN13eQBvm*g@-o=P8}7E`(c;(j4UgFyF* zST%Y^r*xm__d9jE*YspDC+c*agIK`eWz zV9(9lFw8GMHOC8=!23li@+QcV`)3a?99(#ud8~X-z9WzGKd0aL3q=GUy$Wmn2O?0^ AmjD0& literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/environment.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/environment.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..904f7f1cb8fb9470df2b8e566a015beef2ffa8c6 GIT binary patch literal 76654 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 YA)>G@L$(;lAiR!V+B(0a7k=#j2e)LW5&!@I literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/ext.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/ext.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c58fc377d3d79c4e802f9cbcf5568e7e855af59 GIT binary patch literal 41896 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�Vw_h&=xl)&C51 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/filters.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/filters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e95fbb67907cd0287ae5de3ae2be415583a60317 GIT binary patch literal 72288 zcmdSC3w%`9c`v%>sgXv~2)&SaZuCGBh%s-l4A|lg#`ppFAw#4=drORvX5>91Kw{*` z*hwTqi-?_Aj&UP5PJ@fnkS1;H_VirSb9+vDdwcE(kswbwy`=H&ZSL*82S+}w?e^T> z|M#u^8fk>VcGA=NNqg4pz4m&3>s#M@{Y740j)c#J)`lZ*UX!Gs(+}fQs3#^X7D>`e zk|H%pibHXBI+`5(?QC-Lx2ws;-|i;2{_VlHtJB++)s*F+cif%6uI#34PJ23Yy8KOk zPJ5BgZOY|zR%c#Uep5cDeMkqI0-Vl9x}d25=^Vx1IjgI%sgTpTNEbB~aXJs_;-+Fw z=ObOxRKn>%XKB~$rrDe>=q&3hZz|{Xtj;-IbDQRJx)AAkP4hTig!KHT`J66BdO_0y zPM09PuxTNuOOckFWKPdUdQsCNhopKswi?4`eQPUtJSv;2(Dt&<>aN92i}}6s&YG^; zrdm$VLA&aj>Nq{Ob4l0Irlk(aDeaJydCy78e6_0MajQflbwd= zjJ32_Ei+Qwt}49oIkliIM_Kxur|E8t*Z=izWW4UY_i3+V_fc0 zmd!XjpA0|L(#&;#;=6$4ARu{zQmNbp$PVE5a{S&1NS_IEJskQn`1d+%2}>4fwrv#!<;UCM$s{7#FGK;u|CE>3H7gsr5a>NSN<)uieT$ zXv22kDObmDTJIVuWdrUH8^5gYH#eGia2Jo$|ETdL59ia9;+qm6t z8>dFJanFox+-%~~2W;~imtq$Gp2s*-%goVGwxSi=l(~R#`hcyD zhcWhr>chr3^Ozd-?d6gCY<;5o9<h}oBil!rfu@*lyN?E^gQ`xT7UBWU}6Tif^l zit0FEtK$IAz<*`R;ZaZzk1@=y!U}pEu=gmfNI?BfUXz4#Gvmn?1A?t@#$lU)9}OI%1EInEk@e#femz=@Jr>;b8d5oc#=owN&D!Ce3-*Ydtkj@g=d2R(weglEXHj^VT8mQRn)@HF!(;1ao1+JOp`PTO3zV*Eek9py=s znF&hQ;~n0Ml`gb8qK{&Z62&Mg)?6tcDczvviHdj%aG@bz4C(j`Eww0#e5J?M9@SRj z$BZGVM1r(qDDn6VrHL^+p|>qx>9v*EHsdVznX}lg@2TrSznsL(A5s2kM)+8Qwj>zx z*8>;zV-&*5Ki?vEGa#xNQ1>))b|Bw6<%{S=CdeC@;l7|7o?HR4;Ltawe_r-$32njLZK!`qr%cCZ3*EhI!O{$~ioJiaFu0f)joL?Rd|^ibl%h zgku7Fl>g7%TM5T!f@+hGRPLO+Bif@%c@g{V*KGZZ^Z3QB@k4)&cI@T7=2zE_uiM(u zqmRMA@EE)Vn(VXeb0a$6o};{MtK%5=^;lY8jrv4iGvU5ZmN`H{P$cCQ zK=28!`Giq3?mLw8w%wqY*N5Q8UPb;s{Wq zQF}hTKU42A1Gjc~G28#1L&v{Ln>WiR-KHbRw>PJ0Cxo{h!ZnQoVT>g5f<6jpHzF`}Uf!l<^ zf4x!p^}+M++TeMBar+ky+-}15zuq`}nsstVJRMf_#Wcv!S2T9*JFZ(H3B#MV(w^b* zd?tN7mA#*$2qr5 z@7qfL@@H=64{W7

a+ e1w3R$)lNS@klZn>&1KiI<{TUoz05ruP+Gx8WUgreu z0|vcfea}$GLY;B~$29EdD7Nw&sawm!nHKtjk=OdpT-&WRMCi?rK!Kh9SLnrCXz%CL z3*~L*ojNMO-_%RTxo zdi3wmqu&fm%AcrbUvNN^_B5{lUhT&9e^U40`i|O!>pFE9*G=l9xGv%AW%WU%|5VM# z^*`|Se^y(N?otop`e$kfuK!Wpi0gk+kK+1cwdVx~>iGjz!LL78!?^x0>JzyBg?b3r zr?`gyRqaLkFV(HM{%3VNuK$}lfNN6SkL#Fv0M{#O2-k7-3%FiYpTqSh>L9K^RX5|h zL+yXT@olQpkCBS>VmYtf$W!@Wp))mUq&TYUqm2SVD> zo^IUp#iDI-yr%tv)bzNK7mD>pTAPoBv~VaA$Bmp&PdwTj3ZqrlujBarh9i~B&+xc2 z9HYEN+qw0PT6<5I8i{Yyv?#rleITN!ZQ+QjP@W_4uFhDfO>ORoMI+5hPgi#gw{yEg zu~>6!G!j=&P*DZ^Ta6qGYtaZ5hI_Nbyk3hGvU{}7=BIk1aTS@p z_z~`-lz&@VDgJFJV2fE%mX$2t7m6r{qbF3w_C_m>weivI9i+)J+<8e-C0MseWZ9y+ z+MJ3LtHj;ws&l8Zc7|d*_dl>F<<-=9j}~dwjUbG2Br?gEbaNrS(i@JwZ{U)0$JEZY z7-f||4sU5VcpwHyIJhs=b#SX1I~tF6AKVvKRQVw-+M%|_V+Xf~V)4d@cFD(L^7hV9 z?C8Orq1L0SCU1>)L=Og!Vek&_2_HTf!`vRT-(3>4J?$n|!F%K>S(nM~-BoY8S54{lyb*atGshsBKuBg(}N$K2X41TCnf8qjA zX?1u(tES5mx=?Z2BI8nnL@Xbd=cJoXNA|3d)(<3niL8_><=7s?{CN&Xdm>88aevD3 zXv%}h>ru4|JWd-_Y9tTpF(h7+;^u%SBnaH}kD;g4YU7ljKOJ5pWqrqeMmiyN_>Hvn zZE)5`dHbYpvi{UoFRBmYNiL^GfbcK$p&9q># zZn3Uff-->cn5xBNGDa;NX^-W|_{5`fXY{zLwT7s~h$0JMipf#n1Wk_jcB`l+wk#YA z)_GGo;aE5V1Q2OewIJG>a>P?^yc*Y5(k~Ae^@c-Rg#uU2y{`L`Gu{3|Jq0Y|G;Z7AOI26ZJ zM2`Y5cQZn64`X@BX7A)SE!riAc-9C>0e43a(;~G{xQ0c7nQP!&nFX(vWk&ai%OzE0I4~ zedgpCxnhnpbwkd6=}f&_8uC2jbK;tiF6w3X^IFoX@OqsK+m{;WhOWVHjf@QIyxM*E zO1Y`e+9tYv5B>J=7-$=jj?t2qqt*>&*;tOu*jAEi=n}SxR9aP9}Ts|C- z{;IfqaO*_Tf@?*K$BGt@#xLw2FIqXc^+up{EKu=!-=(U5m;IBP%@ea1oO9pImP+Q_ z%$0IyC;b)g7A+Wwj~7)@)}lc_uMl*othrlL(NwYid1*9@maj#9DOY2p7d@mNX&dN5 z{Sm#~Myaj1aQ9k36Ug_8?ggfFN!y8Bw31`%aQP=J<5DTFdi06$+`9(0-E`-8iqFsb zX35JXHzg!rU-6waudVqwuJd_83bH}rc{0X-K9_O5A0&e8! zcR%mN8aEMjM~;!#`c_=F=egE>BV~J<;cXTQyMZITz!5IR4IJS~c=n-g2iJdGs`G^2 zz);))!f1_lb%o>@0RV_SvCyprtz!`^;yc42yr8Ed5!yX6A`msydc?vh782o77!=bG z3FEyCNQATR2W`q5vc5GT-{D@yaAK>%Zxe}z38fODNJtPwo)&7IHUy$DpeQbV+ldgU zVu3ZyQEHGYD7~Vdt}83*^`F6TB%*37g3HBy@pENxIWnm6RvqU#>KS)T$9e?XjP*v5 z_?nCCm)ji{4Kb8;QO8bw0Oo#&h2N|vz>;@K(m~J~{Z0Tgz?T5}5M&Pho*{3hMUZeO zJjO!lcb_Q*HuNzz^jY9e+fu@ja1t&!v%(EryH7H3qpMSmq_WirXl;TfZ8P%JWee=+ zA*2~gIl`%YbL1G$q`dT`Ijn6#Zh>bArwF{6jT8|cj5`IjMp&n>juA{3HtevCY{`T< zNdz0~zy+9dVphqp_k7XFfmh~?6;uv--dnWf!kV8hS}~S8f3R`z>4}o^!R0&UtUTr1F}ZUa4SiGO+mF@~Y8C$II)7y*FmhAKW&)==q&D zi=>K`H%q00lEGYoS+g|WA(-&-#JW4facw)Ap%GTq2%o3idx%NNnu=Q=p$zxY#X_)z zcYt6q4myS{$v6Poe&l-;|FH-z9|O-WAabeh{JyJMize1=`cd=uo8Q?tzGl~u=f5VG=#&S0iR?hd77+4ueVCA*1t$lOR_p0Bher@AF zO1O1X7Kr(yUsg+b_cB%f-NMF=?v$HmIE@%KG4=1ET`yrn9ROC{_en%?1Bl&Q;%eY$ zhmM~Y(lP*UH!(CS(mw`qtoh>vPy7Lipf8{Z-U1F^#5f$loWMdeajFA2)wwt22-bP5 z>C|Y3Qd!}c5^fL2Q!dakbx!R7-E^FY5p6Ha0#GDPV$M)eeAOVPcxdVxbz^uXS z_p)XUSDklV&6+o{w&6z|-|u**=!enq!1nJ)FU=ZR^v$}L>&`EI(KX!oqW8jzv4Z+b z(Xqhxq;LC;+=9Wi0}p)kOOI5r!@&c8p{lXa-B%J19*Bgaktac8*ULKZbSP~mXgqn( zd>)Ck-4d=2#^TqiEQFGomIZmFGcmUs@qWn~@wmyhSfrIP2cUCZeOtCgz>&17GV!T0 ziw3}WU_01<1ep4GxU*9R?{XOQZ?CLGdx#7c@&n*#$OiB`78Ly5B6JP>N7j*0j37}g zIiftoV*(LoNVL0=vU48!O0=2VAPUwF;rVIGvl^EH>AkG{!Jgp`tflJ(vtII^^A7ip zR*x6dUMs-ttQap?1(f2S^32k&*g{~m&=HuL2ks2c=dclM zm5ki>c@3=spgc@65LpSuL(Fv&Lkiv?Bp-NW4|51ISkb4zMfZYN#iYvz_HSRdUf*;# zhhpJY`M4J9hO`M>3GvqY5xEiD+8FobRl^(d_)O2R5;;>}{jcnS7gZ{lON zuAAMixT`|O0*XZAdZU6^Vf!Bf(WimKkI8XO1v5#xz|nK*71b-ZSIFv9JzU>0=N)J2*Fo$hFaBHlnttg3Z)@I1O>pswGLmy&4hT;FO%f87BD7KFgw{B+SQ19qFdY4w-ETl1Sg9cF*AG@@5QJSe@EoVP03m8efPorm*ej| z_2Wfn?jC-4B!1OjmCUIgefag>OS9fueYNJkQn~i#YD6Txfd3e2W<2pU~!`#*`zLaz#6plq;ehhqxHaJl<;G{Y@HT6Ywc$ z2=@TQoFGRfYWUPoENJau)?S~9ny}?_UeqzQ?ViiH2NIYD8z10Olgday$ZFuwSefm< z%et@G@52Dg%%5xRj#e9iY5lgn(xg+Ey0MRkXpCgbYqj66Ka>&~+h6Aj{WH2M_(Y)L z)@XOHAqf)P6AStw{gI=stv#9s1gUd9a;)dDz&X51^(C%r20}m!$K_~54Pw(K)qw`) zDx?eF2?+r?OeugRS+OJ0o=$LTD!4XA8A`p#szUy*7%%G2NcOHHH^D^#BT>20?l_^5 zPW%XKz?DX|EiDX7z&vOG?+e%pBEV_%5;u{1Vr8>;nS@|Qn;HZY2$U6z_JB~KJp%}f z<=5g7aDsgDq+GF6h1^VjEUI-X6&4ocyMzgMM5D^#UbUi5?(gTi={@=^+a&AH#LMZW zhUlZUAF&k~qT-aA=n;I?%PTV#D4vR|c%Q5M#lPUg^bkpENo}IjJj}3U>3!A+*=kHM z5y=25_$b{Vrb_Fg3-K9R6c?sKxf22_!pyqaC?t;OzfXCd#RYf|m?dwZf1;p}@I=uy z|AH(21=jvkQh@!%bIx@^ydV z@T!rVtNzOC1+zySBMnyz7QdHEukRXiyq8-%{P5}f2OT&3xo7Vly8Fe)M_OMv^!m;V z`@Z$Sr8VRJyOZv_r&DFLvgv=42)%Tw?4phnI_UY7k3sLSsC~XJGM7VQ7bohVhiDd( zV+VnRX#`Uqz6Gf_Gp1CwzK!?@8B=!c1aAHm|C1%Sfa>%Y4L6>?=bC@sn19~5e?ii{ z;MdmyQ`~QKKwFPSX~%J~6VB9l&Z?cnlfR-Yy>wZEOWH0@n^-0OC*QSGc2v?uNQ;u6}Pqu*tI*Doiy7MLHmpvZP7`={l(ism^1+LJ8AY=`c84=VW zbf0n*1PtSnX*3**JP5Cbpp_shI?<|j z$K^0lh$lEVuU?SALVpRGO@^j64hoHE0BR4goFVJdt78UAkX`f?)OTA?CzVNr94Hr3 z3s4(Ooi8AlaWQJqK<3eAz}2F%FbTj21w*eJjZo)ylm3p$d61Z-1_GDX4obO;2>H%X zq`e1fK9mn4Qvs6Uf`}MB5;}$+qhD5k=;&lMQ}q^cO)c%}Z6+E?%z<2;j)XTT@l8PDcw;Jh-k>P-MC_0@ z)QvjSh(hW#TTvVCqGoVArW3iVf-CsR-oBbG7*yVlpjex)f4xK#Rf6YDbihJJ2F>1)E#+=4t$K_lH zgb$bVyh!(DJ$W#8*QSk!NKM+-8EOXwtw8IB{UrvCui8pYH$EgQr{9xJSfDRS?GnV4 zj!Y^Y1J6Qysptg1n`Gb56xb*=&veC(<~UOxC%Tb89j5fhxC3R8DaU^%-=d@xN5cJz z^Hn?IaAo3-<0da`mBZ|Yb>DMQM?Sdkdey8A3`d(=QrsQ0thN{_i)QQhCY)MU!mD`{ zcfv~wFx-(kXy~VWAlstdf!M1%a70j!;Ik5X|?DpaLJNKN#Pw^vP7(DqL3mgFa%b9OG^aAAJ7!^xAl+DgfcV#XVi3m@wgOIzboUqyIAGq8b-IzT3Gfwi zGK;)q3Dz#RFN!7t;prSFv96&=FRg5@9JFN|HYub9VA!ljlkY-PW6{pA(gSw3rDYR- zw)41Y%YeZmatyj@mL#;aY{WD0$k5brY2t~fFBI-1-W|3wgx`3)czpub$&aY*Ax(j> zO4q%H1>daqQ4}S};fP8*i>+hSEut!Ho#M81VWcRZp~k^L0!M`*ZqYS5Oyyb_G>v@C zh(UWgf#*)vw1TmZ)ilU8K$xT`si{XY93v48?$TV>bAy@g(=Rn)h2Eg-%c_-{1kfVp zyw;G>)%7P%biuGFt^vDRXgU}av^*8E;jSJ7b3UTRAme96ax6-l1Mex}qqLudV>1xs z)FX>>-hi!+`?0Y??*+sm%Ek(A;OdnXpV)@R81Iv?*<4=7ii&(856YDZ!e7-7JpRITo635@11-F26}{#wznYs~Nn=*cO0=QD#9j=Y7?? zA`G)K%3(zuEeIQeA|sv#xzD+=BIUrJT;~^?9V=M~A;Q{+psE>*1dK<@8&gAC>ycEB zwfa(C5+Ozu!Ow*8j;7ItwgsqRn6r6?Zk(bE%THV=jfE#Er*we-}e={J>mTwkHIeBNxhRTMkPcIzU{IjfrbB|2q6%4N&dVJ7% zBP(z%YtEIdIoI=whBu#X!X1C!z^>^8I_<~2Cw~S`=!q?%?r%fHJtK9M}!&HB940@<8s`9-l>r8fji;^SpfElfWS|KecGg zzlg@%UCG1Hmsk7bvV*bOlLw#NaA;{=tG;jJEeZbm`luw4fHF2Cm14>7Gs>APnUN`# zzo^$?(_<|adMvFb0Z9=F9y3ub20es4umhNLOOr678lc}=CiGiIjcFP#Pv4-y*T^t# z=~RhhRSdzbfyVTC+!e%R9*=53cszYD@X~};5zml0m2xMWl3{nFDb>_2Y_){F&Dj^2 zV9EriBn@1ar1^2#3!0Z!DNIzTzUISfJ9O$OqnXvISmmsVW4>BDMsE>s3J_uO#C=Go zNjr%Q=H6)}<-DIjLPY{g6(Es+cHPiA2pP&(Tq|2UR<`zsC*E=V;N;b^Et4L1cFxbr zwhX)94HVt;pC@@a6JV`>Cekg(ZGIS=>t&55hQJUK`YcNSyky zJvHfQE6=T;>aGr1pODKv=os|CSk*^1Z#IeOEuk7l2(CA?Es1PhP6+Pu(!MBQQP<#* zePVA|g+J07?ble3#mx{Z2J8+SaHkc2+Kw{q1oD-aRm~@NmGfm7UK$tec`N~e zr&zH<$f~dY2>=QaiOkINWMPhU#)ua+=@j*4J%iN`ZM;SJpRC8_Yfdth7~&Wpuyu4o zv1@Kgc6EVef#YCn**doB{OO877Fj(ux|`Ovb-`3xF35n6q?N=JR8O zC>Nj@Xsjj*iih`|%ZAxVVE*;ug`=MF;>Cm8AR5q(Ufytuq4_$p19=l~%gXnZXMYdG zM#AjHOGT>+6IjS(HdFf=Wv5rQZ_wpsT!i6d3 D(Qd!3vD$AT$K%vILiis6kq0GT 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∓mEq2+tYnT0`WXG$6fS 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da068bf77099316089559ce44da90514b0161a02 GIT binary patch literal 32084 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 Y3=;!)1-jq0?MLe;+WuLfJ6mo459LTBEC2ui literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/meta.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/meta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e9c3c84bbd11b35e4c764f51793c80924a4be74 GIT binary patch literal 5489 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 BPNe_< literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9b0f5dc3f9b6f4b90ef167d0e646c59451d931f GIT binary patch literal 2708 zcmaJ@O>7)R7Ov{)>G|{6PCUj=0!v9Tm|?TC;U|cVQCKH&AYpAH*&H$q+v;@J*gfv) z8mfBy$FPc4k#G(yv9v1_X|)LmiJ*WsM>rw4a>1KLv05NSLL9ga>#T@4@v3^-b|OPb zQ}wFq_51x^z4;=a&mtIC?ZY#|K_;7?J#R@ipkA)U07ak4w#RShS1UN7e~^c-@! z&LbyJjcbYM!*bW6R_wO^jS-rfp@cO8x9%=cP6F;yK>|i>=8M4c15#z4L+C<-Qz2Y9 zB$n@xI&<9WA_?Hz(aEW->yw6O*^~$dXI4PmfQT8^gfGuhQnf@NMKeO@EU#$=w3N*r zrMA@+G!{V|26}xKNX3B#0xi!|m%?TZ*PkY?UuC>*1z>|R0d!|=R&Tf-$eyv3(N!00 zQwFeRaDdEHVq3lx>ab0D9bc6o0W8^h@UlcsFb-Z?^@c|e5Rb72Y5JUbo>VPu^M?a6 z&-g4j6d4{}Q%5n2?7Z@J#+swVA`qM^(KD2Jomm_O%o z=GVcFP?wBhj&2=s3DwZxG-z_))}+|ZkQWjs8-Iam6$Mh48ZCUSL%*&C9V8)C)8qTC zHHhl~)zVw1G1by)xtQKsTPT)(tz*q3xVZFZ89NCIx)6{V7h(!Im9PTzK!g}+P%eE2 zP!K?D;My^qRf@9&##u}90&rwNV_DPg9Q8{@Bh0)Q!#~6jDI4I*rB9(QgBm91T;T@A z1W&;km+pj_&4Lj!44~ns*<*(*FA2&;Wzwowj#DukutsIlbtpN_S&iC(sGI=Fqo+@j zIYCZ%mYA(PZ`ree-s7ysDkZRHuJR}MYz4H@-!*sbN!n2`0qD0mKuv;dULlM z)XR549DwN@x^19cd*A-;joydPo%}l2_104tp8ESktBN1UjQWjJ{Q5>HVX!vm%{DMy2cB;h3Bp;1p?{7JMrIyH8B!kpwUoa3G4 zRMX3-iXG#8R~e`UT+RRh(p*A?XJEPuq!J;R>>-Ap)ROb zRU08cBZTjQB}|*9>$`zzF74h#Kq)@EA2@_`_8eN@_wcg*ReF&3!B(Ll)GaW(+_wP8jusFEn!0OLv`;19ZGVTJy~xsXl!Ra zT1@i$BtjpJSI#T{`vO+;Ce<<@>)LxPSGvmDg?# zJhnFQ*m_}bb$n&~gQ+V+S6+R0_@aAQBfdr@y;Id|uF3KjO;?AyQAxBq_#=lj=t3%7dumUHbCYxDh5r&dn26OtZ&j}p~ZZd zGEIlsV73t~%6}P=g^|f&N`Z`VF7qDdr_P)nGmkw#cI<_d6VIs_sTLY}CS$2yst-`4 zAbMe*iQ;mEAB219m>7qtoxm9Xprx_CQ9!uwJJk0L8oHIx-+1<~&wisn(l&H_=;EQ( k!z+g&o%qnzakYF*KdpUIgDm4ix4Zjs;bPDK5iCmG&&7g`b^rhX literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/parser.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/parser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8a5435c548d9ae8911fd277cf1a6837dc00f815 GIT binary patch literal 61199 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 XgJmkyX!_6YVW+X^Ck8q*kof-qnJRSe literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/runtime.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/runtime.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ac83802756a5a83e07fdd5f4db54dbcc48b47c5 GIT binary patch literal 48903 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 E2k{-%m;e9( literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf340002e384f82eb42fa90b71387f7519c32e5b GIT binary patch literal 18116 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/tests.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/tests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b4c850bca825f1b0afb7c552dc27cdc41765dd3 GIT binary patch literal 9069 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 F`(Fu(9J~Mk literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/utils.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f96b84351b433f918f446f70d9b8cfc7ab4ca9dc GIT binary patch literal 34817 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 E19BA$*#H0l literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc b/.venv/Lib/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..568fa7b25dcee80e058f1892bdd0ca60d4f6d3e1 GIT binary patch literal 5372 zcmb_gO>7&-6`mz`Ns6LE%VO-oq#}n7z}dGmZ{E&(^M1bJ$1N>E0&OvQeEiE6LjH*_)uvR4)de6f5{(QKP0~DR zY1pHAH975_l!s-BFi%Rzs4jD#)-J*88}@t1(?nBVA)1f*Q`^>a;oB9jJ2u=TIQ_sG zxZ^*auj+z#=Ep>`nTsOw3J;Y|G>smb7)#hy}mGPY--0K_8VIc>p9O-QBo- zpz^Ys0OBHHWLN^zcr=N5H4l?FGlu5fVqD&Qd^7+ClZSmdU)1lkyr^^CKB=bD>e)1N zWK_+ORcnkpzKLn~X)!x%8Oe&(DjOsu4AiPDwwWWBrGd*NDmyY~=~2rMqZX=SH9^Ob zi9-92e)9Op3mmLDGN?|D^f5kRo0*Y8U1Ri&Wv0MLeB_kMZM6Lqr>D{?pBQ;YO-_JC z`^=O%5}RVi)X4Mt*%8<^JCuPbb&TGp@tGa#RE$-} z!66ApFPtN#GlQ!@kO43VtTWU}7LPl@czn{-vT3A4@%Xu{ns#R_)W?x*$J=YvYT?x2& zbDy{lGxa_(EPMjPWmFbts-TZRDELzM0c%6k{9BBBHy;N-34osj{?9U-KxR`e7;Sc% z(fh^Wn3lQ+-0O#GdV-5MQqOXTyQEEd&al<<5QL0jOi?^jS~+Gqnl(gNaS^Bt!Za5| zKSL*mp{$|BC>?^J-MC;<%~98{>6DtzGES$*_2f96)0NrJC)iVjB2Q6UJ~ zVgG|BRcT_#%AL{;P(GJ%)f4M$7NFB+HmwOkXF*_c%jL0 z)fjUXG{Jn9u0fs?VldYw#+V(i=#!{@3uK)hg-vX*DrM(&Zo7w}id_mxkJj6nCI(D{ zC89z@kZ&+cPrBbo)C9stvmAD%8{B3p7#a`NO)6LnW_H=J?`1Hbs_CD+Sf9UMT9>3S z3t{?SeFnOV#Kxp6S{_P`jWh0>bfq$r!oY55zahW#Xr5|u7$i~e%-cT@331cYc1y-M zXds*rEbS^x^?H*Dg;1$Fi`mshXkP<_t2UexXW}yzL%9~Jodnf+P0~h#p?t^Zy23!z z>-ga`Rok{4+0JE{wH*|VdMr#7!n7RoJD3?0nA2Q$@-sUJ*5Wi)H_-u{BDu|xdqJ{o z=Yn0Bx}+`k{2{fx@7U75W2LU+r9Dqv9r`qUq7*pcCbwEqSa(vGhDA9^196e$Nvd{M zFi>gv+Bwb2dHLm#hvenFG$!ReznA{x(@EZYN&2NUNMM|#8jBUk*o-$S&A1g1Vobyv zSY!|IIxXv5GpelFX|1j~(x@mPXy}UF6k+a})_RMHp+`U{KMma+xf9y99O_sKbrgm^ z4Lv+B-DwWZKlzWg$ih&mt-H{7qpiDmYN@Te6zaYc?wq%m!<|dv&f?xu_~>HfM)>Hv zJC?#nOM#=pp6jfLg+o|z7znVUw3!vJz#O(d^3nr@%*tC@OK!BRL@w->NluD-W{x3X zzyK9a&CX}idQ!J*@mtNy_3mAu-ag`6@kw2$K#rrjK@Xs7d~R$kCQvF!~KixGy(xIF|n{i zl)%qJ_X@e|X;K2^9g1>ND#~So&r-B+RS@O-{K~Py-ZFtl@!VV4H?xAgD!s3~rwH_`@ULnFvYi<@26&5S7E7X;P zFr|tO8ihh7s1_PrhPqUtOyvVEI2g6eN%5(S#Q+CnIZd0c4X6nVt!W*!(e3@bxWaT&*NT4iUT95>ky&3}ffV^u;P=;Y z`)y3JqSd&c5GfQl+R*a;#uPg1&->@33kUzVM7}-mOEotLgV((O3TTQAQt<%r;H{&f zt>a0^BoBIMaJz0N7hbOWsyMF}J7X*KKv8}x_-1hNn|5?QwA68Ek=^X*fvJ_qgUgYwrASwi-HIG6w~`0;t&R>OYx3(8f zU$Sqt9$abfD)hW@{Pp8Sz0}_O?%o^iy;t`wwf8OrR@&N2ZI9fJv={n{UAH2Se&JG! z58R3zE_ad6BjtT$TV%d@BjBjPwjY7w7hT{5RAbu+yk_M(Uz9gSAou10ehdP@1HdiK z{Hzy}=31tQxN0Rf-a* ziWv|8=5Q04n}Tpz)azFF1E7`F4c!(v#bZr1g2AB3(s7hOv%ArI*5(F5DDQ!8j+6uB zz6a)qUK@UOxFDBWVg>s~ORPAx)DoNbd;vc6@Nc!7;YU^?I~IE8pSu%oop0I#u2}e= zgBv?*CyA&1B$gAIf|AqHn5ab8vKAgHXbzQrbzlTgND8X+=Lt@L05AG&# z1r^xJQA9wxf48&@{rF6O{NywJC!ae#@U+wF^2J3?;8?>QkIkF_{EcLrNi*&K1%q%$ zsMMgV6Znq%*UopXr+@{sD({D`?3EgP{=E j;CZ9@_2zp7>3;_vl6vkE= 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/.venv/Lib/site-packages/jinja2/bccache.py b/.venv/Lib/site-packages/jinja2/bccache.py new file mode 100644 index 0000000..ada8b09 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2/compiler.py b/.venv/Lib/site-packages/jinja2/compiler.py new file mode 100644 index 0000000..a4ff6a1 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2/constants.py b/.venv/Lib/site-packages/jinja2/constants.py new file mode 100644 index 0000000..41a1c23 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2/debug.py b/.venv/Lib/site-packages/jinja2/debug.py new file mode 100644 index 0000000..eeeeee7 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2/defaults.py b/.venv/Lib/site-packages/jinja2/defaults.py new file mode 100644 index 0000000..638cad3 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/jinja2/environment.py b/.venv/Lib/site-packages/jinja2/environment.py new file mode 100644 index 0000000..0fc6e5b --- /dev/null +++ b/.venv/Lib/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 = "

$h 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 a=J}G3Il6y3YT57L%jX=Qar9)%@qYoK;#6b+ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc b/.venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe2fe8f5bed43580de0ca86cef5aae9cfd5ae06a GIT binary patch literal 30239 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 Y&o=s8Yai5ZbZvb&?AqX3%d}|!7tG40HUIzs literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/flask/sansio/app.py b/.venv/Lib/site-packages/flask/sansio/app.py new file mode 100644 index 0000000..3620171 --- /dev/null +++ b/.venv/Lib/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: # type: ignore + """The name of the application. This is usually the import name + with the difference that it's guessed from the run file if the + import name is main. This name is used as a display name when + Flask needs the name of the application. It can be set and overridden + to change the value. + + .. versionadded:: 0.8 + """ + if self.import_name == "__main__": + fn: str | None = getattr(sys.modules["__main__"], "__file__", None) + if fn is None: + return "__main__" + return os.path.splitext(os.path.basename(fn))[0] + return self.import_name + + @cached_property + def logger(self) -> logging.Logger: + """A standard Python :class:`~logging.Logger` for the app, with + the same name as :attr:`name`. + + In debug mode, the logger's :attr:`~logging.Logger.level` will + be set to :data:`~logging.DEBUG`. + + If there are no handlers configured, a default handler will be + added. See :doc:`/logging` for more information. + + .. versionchanged:: 1.1.0 + The logger takes the same name as :attr:`name` rather than + hard-coding ``"flask.app"``. + + .. versionchanged:: 1.0.0 + Behavior was simplified. The logger is always named + ``"flask.app"``. The level is only set during configuration, + it doesn't check ``app.debug`` each time. Only one format is + used, not different ones depending on ``app.debug``. No + handlers are removed, and a handler is only added if no + handlers are already configured. + + .. versionadded:: 0.3 + """ + return create_logger(self) + + @cached_property + def jinja_env(self) -> Environment: + """The Jinja environment used to load templates. + + The environment is created the first time this property is + accessed. Changing :attr:`jinja_options` after that will have no + effect. + """ + return self.create_jinja_environment() + + def create_jinja_environment(self) -> Environment: + raise NotImplementedError() + + def make_config(self, instance_relative: bool = False) -> Config: + """Used to create the config attribute by the Flask constructor. + The `instance_relative` parameter is passed in from the constructor + of Flask (there named `instance_relative_config`) and indicates if + the config should be relative to the instance path or the root path + of the application. + + .. versionadded:: 0.8 + """ + root_path = self.root_path + if instance_relative: + root_path = self.instance_path + defaults = dict(self.default_config) + defaults["DEBUG"] = get_debug_flag() + return self.config_class(root_path, defaults) + + def make_aborter(self) -> Aborter: + """Create the object to assign to :attr:`aborter`. That object + is called by :func:`flask.abort` to raise HTTP errors, and can + be called directly as well. + + By default, this creates an instance of :attr:`aborter_class`, + which defaults to :class:`werkzeug.exceptions.Aborter`. + + .. versionadded:: 2.2 + """ + return self.aborter_class() + + def auto_find_instance_path(self) -> str: + """Tries to locate the instance path if it was not provided to the + constructor of the application class. It will basically calculate + the path to a folder named ``instance`` next to your main file or + the package. + + .. versionadded:: 0.8 + """ + prefix, package_path = find_package(self.import_name) + if prefix is None: + return os.path.join(package_path, "instance") + return os.path.join(prefix, "var", f"{self.name}-instance") + + def create_global_jinja_loader(self) -> DispatchingJinjaLoader: + """Creates the loader for the Jinja2 environment. Can be used to + override just the loader and keeping the rest unchanged. It's + discouraged to override this function. Instead one should override + the :meth:`jinja_loader` function instead. + + The global loader dispatches between the loaders of the application + and the individual blueprints. + + .. versionadded:: 0.7 + """ + return DispatchingJinjaLoader(self) + + def select_jinja_autoescape(self, filename: str) -> bool: + """Returns ``True`` if autoescaping should be active for the given + template name. If no template name is given, returns `True`. + + .. versionchanged:: 2.2 + Autoescaping is now enabled by default for ``.svg`` files. + + .. versionadded:: 0.5 + """ + if filename is None: + return True + return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg")) + + @property + def debug(self) -> bool: + """Whether debug mode is enabled. When using ``flask run`` to start the + development server, an interactive debugger will be shown for unhandled + exceptions, and the server will be reloaded when code changes. This maps to the + :data:`DEBUG` config key. It may not behave as expected if set late. + + **Do not enable debug mode when deploying in production.** + + Default: ``False`` + """ + return self.config["DEBUG"] # type: ignore[no-any-return] + + @debug.setter + def debug(self, value: bool) -> None: + self.config["DEBUG"] = value + + if self.config["TEMPLATES_AUTO_RELOAD"] is None: + self.jinja_env.auto_reload = value + + @setupmethod + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on the application. Keyword + arguments passed to this method will override the defaults set on the + blueprint. + + Calls the blueprint's :meth:`~flask.Blueprint.register` method after + recording the blueprint in the application's :attr:`blueprints`. + + :param blueprint: The blueprint to register. + :param url_prefix: Blueprint routes will be prefixed with this. + :param subdomain: Blueprint routes will match on this subdomain. + :param url_defaults: Blueprint routes will use these default values for + view arguments. + :param options: Additional keyword arguments are passed to + :class:`~flask.blueprints.BlueprintSetupState`. They can be + accessed in :meth:`~flask.Blueprint.record` callbacks. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 0.7 + """ + blueprint.register(self, options) + + def iter_blueprints(self) -> t.ValuesView[Blueprint]: + """Iterates over all blueprints by the order they were registered. + + .. versionadded:: 0.11 + """ + return self.blueprints.values() + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + options["endpoint"] = endpoint + methods = options.pop("methods", None) + + # if the methods are not given and the view_func object knows its + # methods we can use that instead. If neither exists, we go with + # a tuple of only ``GET`` as default. + if methods is None: + methods = getattr(view_func, "methods", None) or ("GET",) + if isinstance(methods, str): + raise TypeError( + "Allowed methods must be a list of strings, for" + ' example: @app.route(..., methods=["POST"])' + ) + methods = {item.upper() for item in methods} + + # Methods that should always be added + required_methods: set[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/.venv/Lib/site-packages/flask/sansio/blueprints.py b/.venv/Lib/site-packages/flask/sansio/blueprints.py new file mode 100644 index 0000000..4f912cc --- /dev/null +++ b/.venv/Lib/site-packages/flask/sansio/blueprints.py @@ -0,0 +1,632 @@ +from __future__ import annotations + +import os +import typing as t +from collections import defaultdict +from functools import update_wrapper + +from .. import typing as ft +from .scaffold import _endpoint_from_view_func +from .scaffold import _sentinel +from .scaffold import Scaffold +from .scaffold import setupmethod + +if t.TYPE_CHECKING: # pragma: no cover + from .app import App + +DeferredSetupFunction = t.Callable[["BlueprintSetupState"], None] +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any]) +T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable) +T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +T_template_context_processor = t.TypeVar( + "T_template_context_processor", bound=ft.TemplateContextProcessorCallable +) +T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable) +T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable) +T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable) +T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable) +T_url_value_preprocessor = t.TypeVar( + "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable +) + + +class BlueprintSetupState: + """Temporary holder object for registering a blueprint with the + application. An instance of this class is created by the + :meth:`~flask.Blueprint.make_setup_state` method and later passed + to all register callback functions. + """ + + def __init__( + self, + blueprint: Blueprint, + app: App, + options: t.Any, + first_registration: bool, + ) -> None: + #: a reference to the current application + self.app = app + + #: a reference to the blueprint that created this setup state. + self.blueprint = blueprint + + #: a dictionary with all options that were passed to the + #: :meth:`~flask.Flask.register_blueprint` method. + self.options = options + + #: as blueprints can be registered multiple times with the + #: application and not everything wants to be registered + #: multiple times on it, this attribute can be used to figure + #: out if the blueprint was registered in the past already. + self.first_registration = first_registration + + subdomain = self.options.get("subdomain") + if subdomain is None: + subdomain = self.blueprint.subdomain + + #: The subdomain that the blueprint should be active for, ``None`` + #: otherwise. + self.subdomain = subdomain + + url_prefix = self.options.get("url_prefix") + if url_prefix is None: + url_prefix = self.blueprint.url_prefix + #: The prefix that should be used for all URLs defined on the + #: blueprint. + self.url_prefix = url_prefix + + self.name = self.options.get("name", blueprint.name) + self.name_prefix = self.options.get("name_prefix", "") + + #: A dictionary with URL defaults that is added to each and every + #: URL that was defined with the blueprint. + self.url_defaults = dict(self.blueprint.url_values_defaults) + self.url_defaults.update(self.options.get("url_defaults", ())) + + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + **options: t.Any, + ) -> None: + """A helper method to register a rule (and optionally a view function) + to the application. The endpoint is automatically prefixed with the + blueprint's name. + """ + if self.url_prefix is not None: + if rule: + rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/"))) + else: + rule = self.url_prefix + options.setdefault("subdomain", self.subdomain) + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + defaults = self.url_defaults + if "defaults" in options: + defaults = dict(defaults, **options.pop("defaults")) + + self.app.add_url_rule( + rule, + f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), + view_func, + defaults=defaults, + **options, + ) + + +class Blueprint(Scaffold): + """Represents a blueprint, a collection of routes and other + app-related functions that can be registered on a real application + later. + + A blueprint is an object that allows defining application functions + without requiring an application object ahead of time. It uses the + same decorators as :class:`~flask.Flask`, but defers the need for an + application by recording them for later registration. + + Decorating a function with a blueprint creates a deferred function + that is called with :class:`~flask.blueprints.BlueprintSetupState` + when the blueprint is registered on an application. + + See :doc:`/blueprints` for more information. + + :param name: The name of the blueprint. Will be prepended to each + endpoint name. + :param import_name: The name of the blueprint package, usually + ``__name__``. This helps locate the ``root_path`` for the + blueprint. + :param static_folder: A folder with static files that should be + served by the blueprint's static route. The path is relative to + the blueprint's root path. Blueprint static files are disabled + by default. + :param static_url_path: The url to serve static files from. + Defaults to ``static_folder``. If the blueprint does not have + a ``url_prefix``, the app's static route will take precedence, + and the blueprint's static files won't be accessible. + :param template_folder: A folder with templates that should be added + to the app's template search path. The path is relative to the + blueprint's root path. Blueprint templates are disabled by + default. Blueprint templates have a lower precedence than those + in the app's templates folder. + :param url_prefix: A path to prepend to all of the blueprint's URLs, + to make them distinct from the rest of the app's routes. + :param subdomain: A subdomain that blueprint routes will match on by + default. + :param url_defaults: A dict of default values that blueprint routes + will receive by default. + :param root_path: By default, the blueprint will automatically set + this based on ``import_name``. In certain situations this + automatic detection can fail, so the path can be specified + manually instead. + + .. versionchanged:: 1.1.0 + Blueprints have a ``cli`` group to register nested CLI commands. + The ``cli_group`` parameter controls the name of the group under + the ``flask`` command. + + .. versionadded:: 0.7 + """ + + _got_registered_once = False + + def __init__( + self, + name: str, + import_name: str, + static_folder: str | os.PathLike[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | None = None, + url_prefix: str | None = None, + subdomain: str | None = None, + url_defaults: dict[str, t.Any] | None = None, + root_path: str | None = None, + cli_group: str | None = _sentinel, # type: ignore[assignment] + ): + super().__init__( + import_name=import_name, + static_folder=static_folder, + static_url_path=static_url_path, + template_folder=template_folder, + root_path=root_path, + ) + + if not name: + raise ValueError("'name' may not be empty.") + + if "." in name: + raise ValueError("'name' may not contain a dot '.' character.") + + self.name = name + self.url_prefix = url_prefix + self.subdomain = subdomain + self.deferred_functions: list[DeferredSetupFunction] = [] + + if url_defaults is None: + url_defaults = {} + + self.url_values_defaults = url_defaults + self.cli_group = cli_group + self._blueprints: list[tuple[Blueprint, dict[str, t.Any]]] = [] + + def _check_setup_finished(self, f_name: str) -> None: + if self._got_registered_once: + raise AssertionError( + f"The setup method '{f_name}' can no longer be called on the blueprint" + f" '{self.name}'. It has already been registered at least once, any" + " changes will not be applied consistently.\n" + "Make sure all imports, decorators, functions, etc. needed to set up" + " the blueprint are done before registering it." + ) + + @setupmethod + def record(self, func: DeferredSetupFunction) -> None: + """Registers a function that is called when the blueprint is + registered on the application. This function is called with the + state as argument as returned by the :meth:`make_setup_state` + method. + """ + self.deferred_functions.append(func) + + @setupmethod + def record_once(self, func: DeferredSetupFunction) -> None: + """Works like :meth:`record` but wraps the function in another + function that will ensure the function is only called once. If the + blueprint is registered a second time on the application, the + function passed is not called. + """ + + def wrapper(state: BlueprintSetupState) -> None: + if state.first_registration: + func(state) + + self.record(update_wrapper(wrapper, func)) + + def make_setup_state( + self, app: App, options: dict[str, t.Any], first_registration: bool = False + ) -> BlueprintSetupState: + """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` + object that is later passed to the register callback functions. + Subclasses can override this to return a subclass of the setup state. + """ + return BlueprintSetupState(self, app, options, first_registration) + + @setupmethod + def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on this blueprint. Keyword + arguments passed to this method will override the defaults set + on the blueprint. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 2.0 + """ + if blueprint is self: + raise ValueError("Cannot register a blueprint on itself") + self._blueprints.append((blueprint, options)) + + def register(self, app: App, options: dict[str, t.Any]) -> None: + """Called by :meth:`Flask.register_blueprint` to register all + views and callbacks registered on the blueprint with the + application. Creates a :class:`.BlueprintSetupState` and calls + each :meth:`record` callback with it. + + :param app: The application this blueprint is being registered + with. + :param options: Keyword arguments forwarded from + :meth:`~Flask.register_blueprint`. + + .. versionchanged:: 2.3 + Nested blueprints now correctly apply subdomains. + + .. versionchanged:: 2.1 + Registering the same blueprint with the same name multiple + times is an error. + + .. versionchanged:: 2.0.1 + Nested blueprints are registered with their dotted name. + This allows different blueprints with the same name to be + nested at different locations. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + """ + name_prefix = options.get("name_prefix", "") + self_name = options.get("name", self.name) + name = f"{name_prefix}.{self_name}".lstrip(".") + + if name in app.blueprints: + bp_desc = "this" if app.blueprints[name] is self else "a different" + existing_at = f" '{name}'" if self_name != name else "" + + raise ValueError( + f"The name '{self_name}' is already registered for" + f" {bp_desc} blueprint{existing_at}. Use 'name=' to" + f" provide a unique name." + ) + + first_bp_registration = not any(bp is self for bp in app.blueprints.values()) + first_name_registration = name not in app.blueprints + + app.blueprints[name] = self + self._got_registered_once = True + state = self.make_setup_state(app, options, first_bp_registration) + + if self.has_static_folder: + state.add_url_rule( + f"{self.static_url_path}/", + view_func=self.send_static_file, # type: ignore[attr-defined] + endpoint="static", + ) + + # Merge blueprint data into parent. + if first_bp_registration or first_name_registration: + self._merge_blueprint_funcs(app, name) + + for deferred in self.deferred_functions: + deferred(state) + + cli_resolved_group = options.get("cli_group", self.cli_group) + + if self.cli.commands: + if cli_resolved_group is None: + app.cli.commands.update(self.cli.commands) + elif cli_resolved_group is _sentinel: + self.cli.name = name + app.cli.add_command(self.cli) + else: + self.cli.name = cli_resolved_group + app.cli.add_command(self.cli) + + for blueprint, bp_options in self._blueprints: + bp_options = bp_options.copy() + bp_url_prefix = bp_options.get("url_prefix") + bp_subdomain = bp_options.get("subdomain") + + if bp_subdomain is None: + bp_subdomain = blueprint.subdomain + + if state.subdomain is not None and bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + "." + state.subdomain + elif bp_subdomain is not None: + bp_options["subdomain"] = bp_subdomain + elif state.subdomain is not None: + bp_options["subdomain"] = state.subdomain + + if bp_url_prefix is None: + bp_url_prefix = blueprint.url_prefix + + if state.url_prefix is not None and bp_url_prefix is not None: + bp_options["url_prefix"] = ( + state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") + ) + elif bp_url_prefix is not None: + bp_options["url_prefix"] = bp_url_prefix + elif state.url_prefix is not None: + bp_options["url_prefix"] = state.url_prefix + + bp_options["name_prefix"] = name + blueprint.register(app, bp_options) + + def _merge_blueprint_funcs(self, app: App, name: str) -> None: + def extend( + bp_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], + parent_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], + ) -> None: + for key, values in bp_dict.items(): + key = name if key is None else f"{name}.{key}" + parent_dict[key].extend(values) + + for key, value in self.error_handler_spec.items(): + key = name if key is None else f"{name}.{key}" + value = defaultdict( + dict, + { + code: {exc_class: func for exc_class, func in code_values.items()} + for code, code_values in value.items() + }, + ) + app.error_handler_spec[key] = value + + for endpoint, func in self.view_functions.items(): + app.view_functions[endpoint] = func + + extend(self.before_request_funcs, app.before_request_funcs) + extend(self.after_request_funcs, app.after_request_funcs) + extend( + self.teardown_request_funcs, + app.teardown_request_funcs, + ) + extend(self.url_default_functions, app.url_default_functions) + extend(self.url_value_preprocessors, app.url_value_preprocessors) + extend(self.template_context_processors, app.template_context_processors) + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for + full documentation. + + The URL rule is prefixed with the blueprint's URL prefix. The endpoint name, + used with :func:`url_for`, is prefixed with the blueprint's name. + """ + if endpoint and "." in endpoint: + raise ValueError("'endpoint' may not contain a dot '.' character.") + + if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: + raise ValueError("'view_func' name may not contain a dot '.' character.") + + self.record( + lambda s: s.add_url_rule( + rule, + endpoint, + view_func, + provide_automatic_options=provide_automatic_options, + **options, + ) + ) + + @setupmethod + def app_template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """Register a template filter, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_filter`. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_app_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_filter( + self, f: ft.TemplateFilterCallable, name: str | None = None + ) -> None: + """Register a template filter, available in any template rendered by the + application. Works like the :meth:`app_template_filter` decorator. Equivalent to + :meth:`.Flask.add_template_filter`. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.filters[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: + """Register a template test, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_test`. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_app_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_test( + self, f: ft.TemplateTestCallable, name: str | None = None + ) -> None: + """Register a template test, available in any template rendered by the + application. Works like the :meth:`app_template_test` decorator. Equivalent to + :meth:`.Flask.add_template_test`. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.tests[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: + """Register a template global, available in any template rendered by the + application. Equivalent to :meth:`.Flask.template_global`. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_app_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_global( + self, f: ft.TemplateGlobalCallable, name: str | None = None + ) -> None: + """Register a template global, available in any template rendered by the + application. Works like the :meth:`app_template_global` decorator. Equivalent to + :meth:`.Flask.add_template_global`. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.globals[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def before_app_request(self, f: T_before_request) -> T_before_request: + """Like :meth:`before_request`, but before every request, not only those handled + by the blueprint. Equivalent to :meth:`.Flask.before_request`. + """ + self.record_once( + lambda s: s.app.before_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def after_app_request(self, f: T_after_request) -> T_after_request: + """Like :meth:`after_request`, but after every request, not only those handled + by the blueprint. Equivalent to :meth:`.Flask.after_request`. + """ + self.record_once( + lambda s: s.app.after_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def teardown_app_request(self, f: T_teardown) -> T_teardown: + """Like :meth:`teardown_request`, but after every request, not only those + handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`. + """ + self.record_once( + lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_context_processor( + self, f: T_template_context_processor + ) -> T_template_context_processor: + """Like :meth:`context_processor`, but for templates rendered by every view, not + only by the blueprint. Equivalent to :meth:`.Flask.context_processor`. + """ + self.record_once( + lambda s: s.app.template_context_processors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_errorhandler( + self, code: type[Exception] | int + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Like :meth:`errorhandler`, but for every request, not only those handled by + the blueprint. Equivalent to :meth:`.Flask.errorhandler`. + """ + + def decorator(f: T_error_handler) -> T_error_handler: + def from_blueprint(state: BlueprintSetupState) -> None: + state.app.errorhandler(code)(f) + + self.record_once(from_blueprint) + return f + + return decorator + + @setupmethod + def app_url_value_preprocessor( + self, f: T_url_value_preprocessor + ) -> T_url_value_preprocessor: + """Like :meth:`url_value_preprocessor`, but for every request, not only those + handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`. + """ + self.record_once( + lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults: + """Like :meth:`url_defaults`, but for every request, not only those handled by + the blueprint. Equivalent to :meth:`.Flask.url_defaults`. + """ + self.record_once( + lambda s: s.app.url_default_functions.setdefault(None, []).append(f) + ) + return f diff --git a/.venv/Lib/site-packages/flask/sansio/scaffold.py b/.venv/Lib/site-packages/flask/sansio/scaffold.py new file mode 100644 index 0000000..3a839f5 --- /dev/null +++ b/.venv/Lib/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 # type: ignore + self.static_url_path = static_url_path + + #: The path to the templates folder, relative to + #: :attr:`root_path`, to add to the template loader. ``None`` if + #: templates should not be added. + self.template_folder = template_folder + + if root_path is None: + root_path = get_root_path(self.import_name) + + #: Absolute path to the package on the filesystem. Used to look + #: up resources contained in the package. + self.root_path = root_path + + #: A dictionary mapping endpoint names to view functions. + #: + #: To register a view function, use the :meth:`route` decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.view_functions: dict[str, ft.RouteCallable] = {} + + #: A data structure of registered error handlers, in the format + #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is + #: the name of a blueprint the handlers are active for, or + #: ``None`` for all requests. The ``code`` key is the HTTP + #: status code for ``HTTPException``, or ``None`` for + #: other exceptions. The innermost dictionary maps exception + #: classes to handler functions. + #: + #: To register an error handler, use the :meth:`errorhandler` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.error_handler_spec: dict[ + ft.AppOrBlueprintKey, + dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]], + ] = defaultdict(lambda: defaultdict(dict)) + + #: A data structure of functions to call at the beginning of + #: each request, in the format ``{scope: [functions]}``. The + #: ``scope`` key is the name of a blueprint the functions are + #: active for, or ``None`` for all requests. + #: + #: To register a function, use the :meth:`before_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.before_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable] + ] = defaultdict(list) + + #: A data structure of functions to call at the end of each + #: request, in the format ``{scope: [functions]}``. The + #: ``scope`` key is the name of a blueprint the functions are + #: active for, or ``None`` for all requests. + #: + #: To register a function, use the :meth:`after_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.after_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.AfterRequestCallable[t.Any]] + ] = defaultdict(list) + + #: A data structure of functions to call at the end of each + #: request even if an exception is raised, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`teardown_request` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.teardown_request_funcs: dict[ + ft.AppOrBlueprintKey, list[ft.TeardownCallable] + ] = defaultdict(list) + + #: A data structure of functions to call to pass extra context + #: values when rendering templates, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`context_processor` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.template_context_processors: dict[ + ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable] + ] = defaultdict(list, {None: [_default_template_ctx_processor]}) + + #: A data structure of functions to call to modify the keyword + #: arguments passed to the view function, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the + #: :meth:`url_value_preprocessor` decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.url_value_preprocessors: dict[ + ft.AppOrBlueprintKey, + list[ft.URLValuePreprocessorCallable], + ] = defaultdict(list) + + #: A data structure of functions to call to modify the keyword + #: arguments when generating URLs, in the format + #: ``{scope: [functions]}``. The ``scope`` key is the name of a + #: blueprint the functions are active for, or ``None`` for all + #: requests. + #: + #: To register a function, use the :meth:`url_defaults` + #: decorator. + #: + #: This data structure is internal. It should not be modified + #: directly and its format may change at any time. + self.url_default_functions: dict[ + ft.AppOrBlueprintKey, list[ft.URLDefaultCallable] + ] = defaultdict(list) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.name!r}>" + + def _check_setup_finished(self, f_name: str) -> None: + raise NotImplementedError + + @property + def static_folder(self) -> str | None: + """The absolute path to the configured static folder. ``None`` + if no static folder is set. + """ + if self._static_folder is not None: + return os.path.join(self.root_path, self._static_folder) + else: + return None + + @static_folder.setter + def static_folder(self, value: str | os.PathLike[str] | None) -> None: + if value is not None: + value = os.fspath(value).rstrip(r"\/") + + self._static_folder = value + + @property + def has_static_folder(self) -> bool: + """``True`` if :attr:`static_folder` is set. + + .. versionadded:: 0.5 + """ + return self.static_folder is not None + + @property + def static_url_path(self) -> str | None: + """The URL prefix that the static route will be accessible from. + + If it was not configured during init, it is derived from + :attr:`static_folder`. + """ + if self._static_url_path is not None: + return self._static_url_path + + if self.static_folder is not None: + basename = os.path.basename(self.static_folder) + return f"/{basename}".rstrip("/") + + return None + + @static_url_path.setter + def static_url_path(self, value: str | None) -> None: + if value is not None: + value = value.rstrip("/") + + self._static_url_path = value + + @cached_property + def jinja_loader(self) -> BaseLoader | None: + """The Jinja loader for this object's templates. By default this + is a class :class:`jinja2.loaders.FileSystemLoader` to + :attr:`template_folder` if it is set. + + .. versionadded:: 0.5 + """ + if self.template_folder is not None: + return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) + else: + return None + + def _method_route( + self, + method: str, + rule: str, + options: dict[str, t.Any], + ) -> t.Callable[[T_route], T_route]: + if "methods" in options: + raise TypeError("Use the 'route' decorator to use the 'methods' argument.") + + return self.route(rule, methods=[method], **options) + + @setupmethod + def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["GET"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("GET", rule, options) + + @setupmethod + def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["POST"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("POST", rule, options) + + @setupmethod + def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["PUT"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PUT", rule, options) + + @setupmethod + def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["DELETE"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("DELETE", rule, options) + + @setupmethod + def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Shortcut for :meth:`route` with ``methods=["PATCH"]``. + + .. versionadded:: 2.0 + """ + return self._method_route("PATCH", rule, options) + + @setupmethod + def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]: + """Decorate a view function to register it with the given URL + rule and options. Calls :meth:`add_url_rule`, which has more + details about the implementation. + + .. code-block:: python + + @app.route("/") + def index(): + return "Hello, World!" + + See :ref:`url-route-registrations`. + + The endpoint name for the route defaults to the name of the view + function if the ``endpoint`` parameter isn't passed. + + The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and + ``OPTIONS`` are added automatically. + + :param rule: The URL rule string. + :param options: Extra options passed to the + :class:`~werkzeug.routing.Rule` object. + """ + + def decorator(f: T_route) -> T_route: + endpoint = options.pop("endpoint", None) + self.add_url_rule(rule, endpoint, f, **options) + return f + + return decorator + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: str | None = None, + view_func: ft.RouteCallable | None = None, + provide_automatic_options: bool | None = None, + **options: t.Any, + ) -> None: + """Register a rule for routing incoming requests and building + URLs. The :meth:`route` decorator is a shortcut to call this + with the ``view_func`` argument. These are equivalent: + + .. code-block:: python + + @app.route("/") + def index(): + ... + + .. code-block:: python + + def index(): + ... + + app.add_url_rule("/", view_func=index) + + See :ref:`url-route-registrations`. + + The endpoint name for the route defaults to the name of the view + function if the ``endpoint`` parameter isn't passed. An error + will be raised if a function has already been registered for the + endpoint. + + The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is + always added automatically, and ``OPTIONS`` is added + automatically by default. + + ``view_func`` does not necessarily need to be passed, but if the + rule should participate in routing an endpoint name must be + associated with a view function at some point with the + :meth:`endpoint` decorator. + + .. code-block:: python + + app.add_url_rule("/", endpoint="index") + + @app.endpoint("index") + def index(): + ... + + If ``view_func`` has a ``required_methods`` attribute, those + methods are added to the passed and automatic methods. If it + has a ``provide_automatic_methods`` attribute, it is used as the + default if the parameter is not passed. + + :param rule: The URL rule string. + :param endpoint: The endpoint name to associate with the rule + and view function. Used when routing and building URLs. + Defaults to ``view_func.__name__``. + :param view_func: The view function to associate with the + endpoint name. + :param provide_automatic_options: Add the ``OPTIONS`` method and + respond to ``OPTIONS`` requests automatically. + :param options: Extra options passed to the + :class:`~werkzeug.routing.Rule` object. + """ + raise NotImplementedError + + @setupmethod + def endpoint(self, endpoint: str) -> t.Callable[[F], F]: + """Decorate a view function to register it for the given + endpoint. Used if a rule is added without a ``view_func`` with + :meth:`add_url_rule`. + + .. code-block:: python + + app.add_url_rule("/ex", endpoint="example") + + @app.endpoint("example") + def example(): + ... + + :param endpoint: The endpoint name to associate with the view + function. + """ + + def decorator(f: F) -> F: + self.view_functions[endpoint] = f + return f + + return decorator + + @setupmethod + def before_request(self, f: T_before_request) -> T_before_request: + """Register a function to run before each request. + + For example, this can be used to open a database connection, or + to load the logged in user from the session. + + .. code-block:: python + + @app.before_request + def load_user(): + if "user_id" in session: + g.user = db.session.get(session["user_id"]) + + The function will be called without any arguments. If it returns + a non-``None`` value, the value is handled as if it was the + return value from the view, and further request handling is + stopped. + + This is available on both app and blueprint objects. When used on an app, this + executes before every request. When used on a blueprint, this executes before + every request that the blueprint handles. To register with a blueprint and + execute before every request, use :meth:`.Blueprint.before_app_request`. + """ + self.before_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def after_request(self, f: T_after_request) -> T_after_request: + """Register a function to run after each request to this object. + + The function is called with the response object, and must return + a response object. This allows the functions to modify or + replace the response before it is sent. + + If a function raises an exception, any remaining + ``after_request`` functions will not be called. Therefore, this + should not be used for actions that must execute, such as to + close resources. Use :meth:`teardown_request` for that. + + This is available on both app and blueprint objects. When used on an app, this + executes after every request. When used on a blueprint, this executes after + every request that the blueprint handles. To register with a blueprint and + execute after every request, use :meth:`.Blueprint.after_app_request`. + """ + self.after_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def teardown_request(self, f: T_teardown) -> T_teardown: + """Register a function to be called when the request context is + popped. Typically this happens at the end of each request, but + contexts may be pushed manually as well during testing. + + .. code-block:: python + + with app.test_request_context(): + ... + + When the ``with`` block exits (or ``ctx.pop()`` is called), the + teardown functions are called just before the request context is + made inactive. + + When a teardown function was called because of an unhandled + exception it will be passed an error object. If an + :meth:`errorhandler` is registered, it will handle the exception + and the teardown will not receive it. + + Teardown functions must avoid raising exceptions. If they + execute code that might fail they must surround that code with a + ``try``/``except`` block and log any errors. + + The return values of teardown functions are ignored. + + This is available on both app and blueprint objects. When used on an app, this + executes after every request. When used on a blueprint, this executes after + every request that the blueprint handles. To register with a blueprint and + execute after every request, use :meth:`.Blueprint.teardown_app_request`. + """ + self.teardown_request_funcs.setdefault(None, []).append(f) + return f + + @setupmethod + def context_processor( + self, + f: T_template_context_processor, + ) -> T_template_context_processor: + """Registers a template context processor function. These functions run before + rendering a template. The keys of the returned dict are added as variables + available in the template. + + This is available on both app and blueprint objects. When used on an app, this + is called for every rendered template. When used on a blueprint, this is called + for templates rendered from the blueprint's views. To register with a blueprint + and affect every template, use :meth:`.Blueprint.app_context_processor`. + """ + self.template_context_processors[None].append(f) + return f + + @setupmethod + def url_value_preprocessor( + self, + f: T_url_value_preprocessor, + ) -> T_url_value_preprocessor: + """Register a URL value preprocessor function for all view + functions in the application. These functions will be called before the + :meth:`before_request` functions. + + The function can modify the values captured from the matched url before + they are passed to the view. For example, this can be used to pop a + common language code value and place it in ``g`` rather than pass it to + every view. + + The function is passed the endpoint name and values dict. The return + value is ignored. + + This is available on both app and blueprint objects. When used on an app, this + is called for every request. When used on a blueprint, this is called for + requests that the blueprint handles. To register with a blueprint and affect + every request, use :meth:`.Blueprint.app_url_value_preprocessor`. + """ + self.url_value_preprocessors[None].append(f) + return f + + @setupmethod + def url_defaults(self, f: T_url_defaults) -> T_url_defaults: + """Callback function for URL defaults for all view functions of the + application. It's called with the endpoint and values and should + update the values passed in place. + + This is available on both app and blueprint objects. When used on an app, this + is called for every request. When used on a blueprint, this is called for + requests that the blueprint handles. To register with a blueprint and affect + every request, use :meth:`.Blueprint.app_url_defaults`. + """ + self.url_default_functions[None].append(f) + return f + + @setupmethod + def errorhandler( + self, code_or_exception: type[Exception] | int + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Register a function to handle errors by code or exception class. + + A decorator that is used to register a function given an + error code. Example:: + + @app.errorhandler(404) + def page_not_found(error): + return 'This page does not exist', 404 + + You can also register handlers for arbitrary exceptions:: + + @app.errorhandler(DatabaseError) + def special_exception_handler(error): + return 'Database connection failed', 500 + + This is available on both app and blueprint objects. When used on an app, this + can handle errors from every request. When used on a blueprint, this can handle + errors from requests that the blueprint handles. To register with a blueprint + and affect every request, use :meth:`.Blueprint.app_errorhandler`. + + .. versionadded:: 0.7 + Use :meth:`register_error_handler` instead of modifying + :attr:`error_handler_spec` directly, for application wide error + handlers. + + .. versionadded:: 0.7 + One can now additionally also register custom exception types + that do not necessarily have to be a subclass of the + :class:`~werkzeug.exceptions.HTTPException` class. + + :param code_or_exception: the code as integer for the handler, or + an arbitrary exception + """ + + def decorator(f: T_error_handler) -> T_error_handler: + self.register_error_handler(code_or_exception, f) + return f + + return decorator + + @setupmethod + def register_error_handler( + self, + code_or_exception: type[Exception] | int, + f: ft.ErrorHandlerCallable, + ) -> None: + """Alternative error attach function to the :meth:`errorhandler` + decorator that is more straightforward to use for non decorator + usage. + + .. versionadded:: 0.7 + """ + exc_class, code = self._get_exc_class_and_code(code_or_exception) + self.error_handler_spec[None][code][exc_class] = f + + @staticmethod + def _get_exc_class_and_code( + exc_class_or_code: type[Exception] | int, + ) -> tuple[type[Exception], int | None]: + """Get the exception class being handled. For HTTP status codes + or ``HTTPException`` subclasses, return both the exception and + status code. + + :param exc_class_or_code: Any exception class, or an HTTP status + code as an integer. + """ + exc_class: type[Exception] + + if isinstance(exc_class_or_code, int): + try: + exc_class = default_exceptions[exc_class_or_code] + except KeyError: + raise ValueError( + f"'{exc_class_or_code}' is not a recognized HTTP" + " error code. Use a subclass of HTTPException with" + " that code instead." + ) from None + else: + exc_class = exc_class_or_code + + if isinstance(exc_class, Exception): + raise TypeError( + f"{exc_class!r} is an instance, not a class. Handlers" + " can only be registered for Exception classes or HTTP" + " error codes." + ) + + if not issubclass(exc_class, Exception): + raise ValueError( + f"'{exc_class.__name__}' is not a subclass of Exception." + " Handlers can only be registered for Exception classes" + " or HTTP error codes." + ) + + if issubclass(exc_class, HTTPException): + return exc_class, exc_class.code + else: + return exc_class, None + + +def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str: + """Internal helper that returns the default endpoint for a given + function. This always is the function name. + """ + assert view_func is not None, "expected view func if endpoint is not provided." + return view_func.__name__ + + +def _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/.venv/Lib/site-packages/flask/sessions.py b/.venv/Lib/site-packages/flask/sessions.py new file mode 100644 index 0000000..4ffde71 --- /dev/null +++ b/.venv/Lib/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 # type: ignore # noqa: B950 + del _fail + + +class SessionInterface: + """The basic interface you have to implement in order to replace the + default session interface which uses werkzeug's securecookie + implementation. The only methods you have to implement are + :meth:`open_session` and :meth:`save_session`, the others have + useful defaults which you don't need to change. + + The session object returned by the :meth:`open_session` method has to + provide a dictionary like interface plus the properties and methods + from the :class:`SessionMixin`. We recommend just subclassing a dict + and adding that mixin:: + + class Session(dict, SessionMixin): + pass + + If :meth:`open_session` returns ``None`` Flask will call into + :meth:`make_null_session` to create a session that acts as replacement + if the session support cannot work because some requirement is not + fulfilled. The default :class:`NullSession` class that is created + will complain that the secret key was not set. + + To replace the session interface on an application all you have to do + is to assign :attr:`flask.Flask.session_interface`:: + + app = Flask(__name__) + app.session_interface = MySessionInterface() + + Multiple requests with the same session may be sent and handled + concurrently. When implementing a new session interface, consider + whether reads or writes to the backing store must be synchronized. + There is no guarantee on the order in which the session for each + request is opened or saved, it will occur in the order that requests + begin and end processing. + + .. versionadded:: 0.8 + """ + + #: :meth:`make_null_session` will look here for the class that should + #: be created when a null session is requested. Likewise the + #: :meth:`is_null_session` method will perform a typecheck against + #: this type. + null_session_class = NullSession + + #: A flag that indicates if the session interface is pickle based. + #: This can be used by Flask extensions to make a decision in regards + #: to how to deal with the session object. + #: + #: .. versionadded:: 0.10 + pickle_based = False + + def make_null_session(self, app: Flask) -> NullSession: + """Creates a null session which acts as a replacement object if the + real session support could not be loaded due to a configuration + error. This mainly aids the user experience because the job of the + null session is to still support lookup without complaining but + modifications are answered with a helpful error message of what + failed. + + This creates an instance of :attr:`null_session_class` by default. + """ + return self.null_session_class() + + def is_null_session(self, obj: object) -> bool: + """Checks if a given object is a null session. Null sessions are + not asked to be saved. + + This checks if the object is an instance of :attr:`null_session_class` + by default. + """ + return isinstance(obj, self.null_session_class) + + def get_cookie_name(self, app: Flask) -> str: + """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``.""" + return app.config["SESSION_COOKIE_NAME"] # type: ignore[no-any-return] + + def get_cookie_domain(self, app: Flask) -> str | None: + """The value of the ``Domain`` parameter on the session cookie. If not set, + browsers will only send the cookie to the exact domain it was set from. + Otherwise, they will send it to any subdomain of the given value as well. + + Uses the :data:`SESSION_COOKIE_DOMAIN` config. + + .. versionchanged:: 2.3 + Not set by default, does not fall back to ``SERVER_NAME``. + """ + return app.config["SESSION_COOKIE_DOMAIN"] # type: ignore[no-any-return] + + def get_cookie_path(self, app: Flask) -> str: + """Returns the path for which the cookie should be valid. The + default implementation uses the value from the ``SESSION_COOKIE_PATH`` + config var if it's set, and falls back to ``APPLICATION_ROOT`` or + uses ``/`` if it's ``None``. + """ + return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] # type: ignore[no-any-return] + + def get_cookie_httponly(self, app: Flask) -> bool: + """Returns True if the session cookie should be httponly. This + currently just returns the value of the ``SESSION_COOKIE_HTTPONLY`` + config var. + """ + return app.config["SESSION_COOKIE_HTTPONLY"] # type: ignore[no-any-return] + + def get_cookie_secure(self, app: Flask) -> bool: + """Returns True if the cookie should be secure. This currently + just returns the value of the ``SESSION_COOKIE_SECURE`` setting. + """ + return app.config["SESSION_COOKIE_SECURE"] # type: ignore[no-any-return] + + def get_cookie_samesite(self, app: Flask) -> str | None: + """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the + ``SameSite`` attribute. This currently just returns the value of + the :data:`SESSION_COOKIE_SAMESITE` setting. + """ + return app.config["SESSION_COOKIE_SAMESITE"] # type: ignore[no-any-return] + + def get_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/.venv/Lib/site-packages/flask/signals.py b/.venv/Lib/site-packages/flask/signals.py new file mode 100644 index 0000000..444fda9 --- /dev/null +++ b/.venv/Lib/site-packages/flask/signals.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from blinker import Namespace + +# This namespace is only for signals provided by Flask itself. +_signals = Namespace() + +template_rendered = _signals.signal("template-rendered") +before_render_template = _signals.signal("before-render-template") +request_started = _signals.signal("request-started") +request_finished = _signals.signal("request-finished") +request_tearing_down = _signals.signal("request-tearing-down") +got_request_exception = _signals.signal("got-request-exception") +appcontext_tearing_down = _signals.signal("appcontext-tearing-down") +appcontext_pushed = _signals.signal("appcontext-pushed") +appcontext_popped = _signals.signal("appcontext-popped") +message_flashed = _signals.signal("message-flashed") diff --git a/.venv/Lib/site-packages/flask/templating.py b/.venv/Lib/site-packages/flask/templating.py new file mode 100644 index 0000000..618a3b3 --- /dev/null +++ b/.venv/Lib/site-packages/flask/templating.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import typing as t + +from jinja2 import BaseLoader +from jinja2 import Environment as BaseEnvironment +from jinja2 import Template +from jinja2 import TemplateNotFound + +from .globals import _cv_app +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .helpers import stream_with_context +from .signals import before_render_template +from .signals import template_rendered + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .sansio.app import App + from .sansio.scaffold import Scaffold + + +def _default_template_ctx_processor() -> dict[str, t.Any]: + """Default template context processor. Injects `request`, + `session` and `g`. + """ + appctx = _cv_app.get(None) + reqctx = _cv_request.get(None) + rv: dict[str, t.Any] = {} + if appctx is not None: + rv["g"] = appctx.g + if reqctx is not None: + rv["request"] = reqctx.request + rv["session"] = reqctx.session + return rv + + +class Environment(BaseEnvironment): + """Works like a regular Jinja2 environment but has some additional + knowledge of how Flask's blueprint works so that it can prepend the + name of the blueprint to referenced templates if necessary. + """ + + def __init__(self, app: App, **options: t.Any) -> None: + if "loader" not in options: + options["loader"] = app.create_global_jinja_loader() + BaseEnvironment.__init__(self, **options) + self.app = app + + +class DispatchingJinjaLoader(BaseLoader): + """A loader that looks for templates in the application and all + the blueprint folders. + """ + + def __init__(self, app: App) -> None: + self.app = app + + def get_source( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + if self.app.config["EXPLAIN_TEMPLATE_LOADING"]: + return self._get_source_explained(environment, template) + return self._get_source_fast(environment, template) + + def _get_source_explained( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + attempts = [] + rv: tuple[str, str | None, t.Callable[[], bool] | None] | None + trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None + + for srcobj, loader in self._iter_loaders(template): + try: + rv = loader.get_source(environment, template) + if trv is None: + trv = rv + except TemplateNotFound: + rv = None + attempts.append((loader, srcobj, rv)) + + from .debughelpers import explain_template_loading_attempts + + explain_template_loading_attempts(self.app, template, attempts) + + if trv is not None: + return trv + raise TemplateNotFound(template) + + def _get_source_fast( + self, environment: BaseEnvironment, template: str + ) -> tuple[str, str | None, t.Callable[[], bool] | None]: + for _srcobj, loader in self._iter_loaders(template): + try: + return loader.get_source(environment, template) + except TemplateNotFound: + continue + raise TemplateNotFound(template) + + def _iter_loaders(self, template: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]: + loader = self.app.jinja_loader + if loader is not None: + yield self.app, loader + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + yield blueprint, loader + + def list_templates(self) -> list[str]: + result = set() + loader = self.app.jinja_loader + if loader is not None: + result.update(loader.list_templates()) + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + for template in loader.list_templates(): + result.add(template) + + return list(result) + + +def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + rv = template.render(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + return rv + + +def render_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> str: + """Render a template by name with the given context. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _render(app, template, context) + + +def render_template_string(source: str, **context: t.Any) -> str: + """Render a template from the given source string with the given + context. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _render(app, template, context) + + +def _stream( + app: Flask, template: Template, context: dict[str, t.Any] +) -> t.Iterator[str]: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + def generate() -> t.Iterator[str]: + yield from template.generate(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + rv = generate() + + # If a request context is active, keep it while generating. + if request: + rv = stream_with_context(rv) + + return rv + + +def stream_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> t.Iterator[str]: + """Render a template by name with the given context as a stream. + This returns an iterator of strings, which can be used as a + streaming response from a view. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _stream(app, template, context) + + +def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]: + """Render a template from the given source string with the given + context as a stream. This returns an iterator of strings, which can + be used as a streaming response from a view. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _stream(app, template, context) diff --git a/.venv/Lib/site-packages/flask/testing.py b/.venv/Lib/site-packages/flask/testing.py new file mode 100644 index 0000000..da156cc --- /dev/null +++ b/.venv/Lib/site-packages/flask/testing.py @@ -0,0 +1,298 @@ +from __future__ import annotations + +import importlib.metadata +import typing as t +from contextlib import contextmanager +from contextlib import ExitStack +from copy import copy +from types import TracebackType +from urllib.parse import urlsplit + +import werkzeug.test +from click.testing import CliRunner +from 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: # type: ignore + """Serialize ``obj`` to a JSON-formatted string. + + The serialization will be configured according to the config associated + with this EnvironBuilder's ``app``. + """ + return self.app.json.dumps(obj, **kwargs) + + +_werkzeug_version = "" + + +def _get_werkzeug_version() -> str: + global _werkzeug_version + + if not _werkzeug_version: + _werkzeug_version = importlib.metadata.version("werkzeug") + + return _werkzeug_version + + +class FlaskClient(Client): + """Works like a regular Werkzeug test client but has knowledge about + Flask's contexts to defer the cleanup of the request context until + the end of a ``with`` block. For general information about how to + use this class refer to :class:`werkzeug.test.Client`. + + .. versionchanged:: 0.12 + `app.test_client()` includes preset default environment, which can be + set after instantiation of the `app.test_client()` object in + `client.environ_base`. + + Basic usage is outlined in the :doc:`/testing` chapter. + """ + + application: Flask + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + super().__init__(*args, **kwargs) + self.preserve_context = False + self._new_contexts: list[t.ContextManager[t.Any]] = [] + self._context_stack = ExitStack() + self.environ_base = { + "REMOTE_ADDR": "127.0.0.1", + "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}", + } + + @contextmanager + def session_transaction( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Iterator[SessionMixin]: + """When used in combination with a ``with`` statement this opens a + session transaction. This can be used to modify the session that + the test client uses. Once the ``with`` block is left the session is + stored back. + + :: + + with client.session_transaction() as session: + session['value'] = 42 + + Internally this is implemented by going through a temporary test + request context and since session handling could depend on + request variables this function accepts the same arguments as + :meth:`~flask.Flask.test_request_context` which are directly + passed through. + """ + if self._cookies is None: + raise TypeError( + "Cookies are disabled. Create a client with 'use_cookies=True'." + ) + + app = self.application + ctx = app.test_request_context(*args, **kwargs) + self._add_cookies_to_wsgi(ctx.request.environ) + + with ctx: + sess = app.session_interface.open_session(app, ctx.request) + + if sess is None: + raise RuntimeError("Session backend did not open a session.") + + yield sess + resp = app.response_class() + + if app.session_interface.is_null_session(sess): + return + + with ctx: + app.session_interface.save_session(app, sess, resp) + + self._update_cookies_from_response( + ctx.request.host.partition(":")[0], + ctx.request.path, + resp.headers.getlist("Set-Cookie"), + ) + + def _copy_environ(self, other: WSGIEnvironment) -> WSGIEnvironment: + out = {**self.environ_base, **other} + + if self.preserve_context: + out["werkzeug.debug.preserve_context"] = self._new_contexts.append + + return out + + def _request_from_builder_args( + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] + ) -> BaseRequest: + kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {})) + builder = EnvironBuilder(self.application, *args, **kwargs) + + try: + return builder.get_request() + finally: + builder.close() + + def open( + self, + *args: t.Any, + buffered: bool = False, + follow_redirects: bool = False, + **kwargs: t.Any, + ) -> TestResponse: + if args and isinstance( + args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest) + ): + if isinstance(args[0], werkzeug.test.EnvironBuilder): + builder = copy(args[0]) + builder.environ_base = self._copy_environ(builder.environ_base or {}) # type: ignore[arg-type] + request = builder.get_request() + elif isinstance(args[0], dict): + request = EnvironBuilder.from_environ( + args[0], app=self.application, environ_base=self._copy_environ({}) + ).get_request() + else: + # isinstance(args[0], BaseRequest) + request = copy(args[0]) + request.environ = self._copy_environ(request.environ) + else: + # request is None + request = self._request_from_builder_args(args, kwargs) + + # Pop any previously preserved contexts. This prevents contexts + # from being preserved across redirects or multiple requests + # within a single block. + self._context_stack.close() + + response = super().open( + request, + buffered=buffered, + follow_redirects=follow_redirects, + ) + response.json_module = self.application.json # type: ignore[assignment] + + # Re-push contexts that were preserved during the request. + while self._new_contexts: + cm = self._new_contexts.pop() + self._context_stack.enter_context(cm) + + return response + + def __enter__(self) -> FlaskClient: + if self.preserve_context: + raise RuntimeError("Cannot nest client invocations") + self.preserve_context = True + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.preserve_context = False + self._context_stack.close() + + +class FlaskCliRunner(CliRunner): + """A :class:`~click.testing.CliRunner` for testing a Flask app's + CLI commands. Typically created using + :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`. + """ + + def __init__(self, app: Flask, **kwargs: t.Any) -> None: + self.app = app + super().__init__(**kwargs) + + def invoke( # type: ignore + self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any + ) -> 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/.venv/Lib/site-packages/flask/typing.py b/.venv/Lib/site-packages/flask/typing.py new file mode 100644 index 0000000..6b70c40 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/flask/views.py b/.venv/Lib/site-packages/flask/views.py new file mode 100644 index 0000000..53fe976 --- /dev/null +++ b/.venv/Lib/site-packages/flask/views.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +import typing as t + +from . import typing as ft +from .globals import current_app +from .globals import request + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +http_method_funcs = frozenset( + ["get", "post", "head", "options", "delete", "put", "trace", "patch"] +) + + +class View: + """Subclass this class and override :meth:`dispatch_request` to + create a generic class-based view. Call :meth:`as_view` to create a + view function that creates an instance of the class with the given + arguments and calls its ``dispatch_request`` method with any URL + variables. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class Hello(View): + init_every_request = False + + def dispatch_request(self, name): + return f"Hello, {name}!" + + app.add_url_rule( + "/hello/", view_func=Hello.as_view("hello") + ) + + Set :attr:`methods` on the class to change what methods the view + accepts. + + Set :attr:`decorators` on the class to apply a list of decorators to + the generated view function. Decorators applied to the class itself + will not be applied to the generated view function! + + Set :attr:`init_every_request` to ``False`` for efficiency, unless + you need to store request-global data on ``self``. + """ + + #: The methods this view is registered for. Uses the same default + #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and + #: ``add_url_rule`` by default. + methods: t.ClassVar[t.Collection[str] | None] = None + + #: Control whether the ``OPTIONS`` method is handled automatically. + #: Uses the same default (``True``) as ``route`` and + #: ``add_url_rule`` by default. + provide_automatic_options: t.ClassVar[bool | None] = None + + #: A list of decorators to apply, in order, to the generated view + #: function. Remember that ``@decorator`` syntax is applied bottom + #: to top, so the first decorator in the list would be the bottom + #: decorator. + #: + #: .. versionadded:: 0.8 + decorators: t.ClassVar[list[t.Callable[..., 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/.venv/Lib/site-packages/flask/wrappers.py b/.venv/Lib/site-packages/flask/wrappers.py new file mode 100644 index 0000000..bab6102 --- /dev/null +++ b/.venv/Lib/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/.venv/Lib/site-packages/h11-0.16.0.dist-info/INSTALLER b/.venv/Lib/site-packages/h11-0.16.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/h11-0.16.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/h11-0.16.0.dist-info/METADATA b/.venv/Lib/site-packages/h11-0.16.0.dist-info/METADATA new file mode 100644 index 0000000..8a2f639 --- /dev/null +++ b/.venv/Lib/site-packages/h11-0.16.0.dist-info/METADATA @@ -0,0 +1,202 @@ +Metadata-Version: 2.4 +Name: h11 +Version: 0.16.0 +Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 +Home-page: https://github.com/python-hyper/h11 +Author: Nathaniel J. Smith +Author-email: njs@pobox.com +License: MIT +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: System :: Networking +Requires-Python: >=3.8 +License-File: LICENSE.txt +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: requires-python +Dynamic: summary + +h11 +=== + +.. image:: https://travis-ci.org/python-hyper/h11.svg?branch=master + :target: https://travis-ci.org/python-hyper/h11 + :alt: Automated test status + +.. image:: https://codecov.io/gh/python-hyper/h11/branch/master/graph/badge.svg + :target: https://codecov.io/gh/python-hyper/h11 + :alt: Test coverage + +.. image:: https://readthedocs.org/projects/h11/badge/?version=latest + :target: http://h11.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +This is a little HTTP/1.1 library written from scratch in Python, +heavily inspired by `hyper-h2 `_. + +It's a "bring-your-own-I/O" library; h11 contains no IO code +whatsoever. This means you can hook h11 up to your favorite network +API, and that could be anything you want: synchronous, threaded, +asynchronous, or your own implementation of `RFC 6214 +`_ -- h11 won't judge you. +(Compare this to the current state of the art, where every time a `new +network API `_ comes along then someone +gets to start over reimplementing the entire HTTP protocol from +scratch.) Cory Benfield made an `excellent blog post describing the +benefits of this approach +`_, or if you like video +then here's his `PyCon 2016 talk on the same theme +`_. + +This also means that h11 is not immediately useful out of the box: +it's a toolkit for building programs that speak HTTP, not something +that could directly replace ``requests`` or ``twisted.web`` or +whatever. But h11 makes it much easier to implement something like +``requests`` or ``twisted.web``. + +At a high level, working with h11 goes like this: + +1) First, create an ``h11.Connection`` object to track the state of a + single HTTP/1.1 connection. + +2) When you read data off the network, pass it to + ``conn.receive_data(...)``; you'll get back a list of objects + representing high-level HTTP "events". + +3) When you want to send a high-level HTTP event, create the + corresponding "event" object and pass it to ``conn.send(...)``; + this will give you back some bytes that you can then push out + through the network. + +For example, a client might instantiate and then send a +``h11.Request`` object, then zero or more ``h11.Data`` objects for the +request body (e.g., if this is a POST), and then a +``h11.EndOfMessage`` to indicate the end of the message. Then the +server would then send back a ``h11.Response``, some ``h11.Data``, and +its own ``h11.EndOfMessage``. If either side violates the protocol, +you'll get a ``h11.ProtocolError`` exception. + +h11 is suitable for implementing both servers and clients, and has a +pleasantly symmetric API: the events you send as a client are exactly +the ones that you receive as a server and vice-versa. + +`Here's an example of a tiny HTTP client +`_ + +It also has `a fine manual `_. + +FAQ +--- + +*Whyyyyy?* + +I wanted to play with HTTP in `Curio +`__ and `Trio +`__, which at the time didn't have any +HTTP libraries. So I thought, no big deal, Python has, like, a dozen +different implementations of HTTP, surely I can find one that's +reusable. I didn't find one, but I did find Cory's call-to-arms +blog-post. So I figured, well, fine, if I have to implement HTTP from +scratch, at least I can make sure no-one *else* has to ever again. + +*Should I use it?* + +Maybe. You should be aware that it's a very young project. But, it's +feature complete and has an exhaustive test-suite and complete docs, +so the next step is for people to try using it and see how it goes +:-). If you do then please let us know -- if nothing else we'll want +to talk to you before making any incompatible changes! + +*What are the features/limitations?* + +Roughly speaking, it's trying to be a robust, complete, and non-hacky +implementation of the first "chapter" of the HTTP/1.1 spec: `RFC 7230: +HTTP/1.1 Message Syntax and Routing +`_. That is, it mostly focuses on +implementing HTTP at the level of taking bytes on and off the wire, +and the headers related to that, and tries to be anal about spec +conformance. It doesn't know about higher-level concerns like URL +routing, conditional GETs, cross-origin cookie policies, or content +negotiation. But it does know how to take care of framing, +cross-version differences in keep-alive handling, and the "obsolete +line folding" rule, so you can focus your energies on the hard / +interesting parts for your application, and it tries to support the +full specification in the sense that any useful HTTP/1.1 conformant +application should be able to use h11. + +It's pure Python, and has no dependencies outside of the standard +library. + +It has a test suite with 100.0% coverage for both statements and +branches. + +Currently it supports Python 3 (testing on 3.8-3.12) and PyPy 3. +The last Python 2-compatible version was h11 0.11.x. +(Originally it had a Cython wrapper for `http-parser +`_ and a beautiful nested state +machine implemented with ``yield from`` to postprocess the output. But +I had to take these out -- the new *parser* needs fewer lines-of-code +than the old *parser wrapper*, is written in pure Python, uses no +exotic language syntax, and has more features. It's sad, really; that +old state machine was really slick. I just need a few sentences here +to mourn that.) + +I don't know how fast it is. I haven't benchmarked or profiled it yet, +so it's probably got a few pointless hot spots, and I've been trying +to err on the side of simplicity and robustness instead of +micro-optimization. But at the architectural level I tried hard to +avoid fundamentally bad decisions, e.g., I believe that all the +parsing algorithms remain linear-time even in the face of pathological +input like slowloris, and there are no byte-by-byte loops. (I also +believe that it maintains bounded memory usage in the face of +arbitrary/pathological input.) + +The whole library is ~800 lines-of-code. You can read and understand +the whole thing in less than an hour. Most of the energy invested in +this so far has been spent on trying to keep things simple by +minimizing special-cases and ad hoc state manipulation; even though it +is now quite small and simple, I'm still annoyed that I haven't +figured out how to make it even smaller and simpler. (Unfortunately, +HTTP does not lend itself to simplicity.) + +The API is ~feature complete and I don't expect the general outlines +to change much, but you can't judge an API's ergonomics until you +actually document and use it, so I'd expect some changes in the +details. + +*How do I try it?* + +.. code-block:: sh + + $ pip install h11 + $ git clone git@github.com:python-hyper/h11 + $ cd h11/examples + $ python basic-client.py + +and go from there. + +*License?* + +MIT + +*Code of conduct?* + +Contributors are requested to follow our `code of conduct +`_ in +all project spaces. diff --git a/.venv/Lib/site-packages/h11-0.16.0.dist-info/RECORD b/.venv/Lib/site-packages/h11-0.16.0.dist-info/RECORD new file mode 100644 index 0000000..fa60234 --- /dev/null +++ b/.venv/Lib/site-packages/h11-0.16.0.dist-info/RECORD @@ -0,0 +1,29 @@ +h11-0.16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +h11-0.16.0.dist-info/METADATA,sha256=KPMmCYrAn8unm48YD5YIfIQf4kViFct7hyqcfVzRnWQ,8348 +h11-0.16.0.dist-info/RECORD,, +h11-0.16.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91 +h11-0.16.0.dist-info/licenses/LICENSE.txt,sha256=N9tbuFkm2yikJ6JYZ_ELEjIAOuob5pzLhRE4rbjm82E,1124 +h11-0.16.0.dist-info/top_level.txt,sha256=F7dC4jl3zeh8TGHEPaWJrMbeuoWbS379Gwdi-Yvdcis,4 +h11/__init__.py,sha256=iO1KzkSO42yZ6ffg-VMgbx_ZVTWGUY00nRYEWn-s3kY,1507 +h11/__pycache__/__init__.cpython-312.pyc,, +h11/__pycache__/_abnf.cpython-312.pyc,, +h11/__pycache__/_connection.cpython-312.pyc,, +h11/__pycache__/_events.cpython-312.pyc,, +h11/__pycache__/_headers.cpython-312.pyc,, +h11/__pycache__/_readers.cpython-312.pyc,, +h11/__pycache__/_receivebuffer.cpython-312.pyc,, +h11/__pycache__/_state.cpython-312.pyc,, +h11/__pycache__/_util.cpython-312.pyc,, +h11/__pycache__/_version.cpython-312.pyc,, +h11/__pycache__/_writers.cpython-312.pyc,, +h11/_abnf.py,sha256=ybixr0xsupnkA6GFAyMubuXF6Tc1lb_hF890NgCsfNc,4815 +h11/_connection.py,sha256=k9YRVf6koZqbttBW36xSWaJpWdZwa-xQVU9AHEo9DuI,26863 +h11/_events.py,sha256=I97aXoal1Wu7dkL548BANBUCkOIbe-x5CioYA9IBY14,11792 +h11/_headers.py,sha256=P7D-lBNxHwdLZPLimmYwrPG-9ZkjElvvJZJdZAgSP-4,10412 +h11/_readers.py,sha256=a4RypORUCC3d0q_kxPuBIM7jTD8iLt5X91TH0FsduN4,8590 +h11/_receivebuffer.py,sha256=xrspsdsNgWFxRfQcTXxR8RrdjRXXTK0Io5cQYWpJ1Ws,5252 +h11/_state.py,sha256=_5LG_BGR8FCcFQeBPH-TMHgm_-B-EUcWCnQof_9XjFE,13231 +h11/_util.py,sha256=LWkkjXyJaFlAy6Lt39w73UStklFT5ovcvo0TkY7RYuk,4888 +h11/_version.py,sha256=GVSsbPSPDcOuF6ptfIiXnVJoaEm3ygXbMnqlr_Giahw,686 +h11/_writers.py,sha256=oFKm6PtjeHfbj4RLX7VB7KDc1gIY53gXG3_HR9ltmTA,5081 +h11/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7 diff --git a/.venv/Lib/site-packages/h11-0.16.0.dist-info/WHEEL b/.venv/Lib/site-packages/h11-0.16.0.dist-info/WHEEL new file mode 100644 index 0000000..1eb3c49 --- /dev/null +++ b/.venv/Lib/site-packages/h11-0.16.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (78.1.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/Lib/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt b/.venv/Lib/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..8f080ea --- /dev/null +++ b/.venv/Lib/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 Nathaniel J. Smith and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.venv/Lib/site-packages/h11-0.16.0.dist-info/top_level.txt b/.venv/Lib/site-packages/h11-0.16.0.dist-info/top_level.txt new file mode 100644 index 0000000..0d24def --- /dev/null +++ b/.venv/Lib/site-packages/h11-0.16.0.dist-info/top_level.txt @@ -0,0 +1 @@ +h11 diff --git a/.venv/Lib/site-packages/h11/__init__.py b/.venv/Lib/site-packages/h11/__init__.py new file mode 100644 index 0000000..989e92c --- /dev/null +++ b/.venv/Lib/site-packages/h11/__init__.py @@ -0,0 +1,62 @@ +# A highish-level implementation of the HTTP/1.1 wire protocol (RFC 7230), +# containing no networking code at all, loosely modelled on hyper-h2's generic +# implementation of HTTP/2 (and in particular the h2.connection.H2Connection +# class). There's still a bunch of subtle details you need to get right if you +# want to make this actually useful, because it doesn't implement all the +# semantics to check that what you're asking to write to the wire is sensible, +# but at least it gets you out of dealing with the wire itself. + +from h11._connection import Connection, NEED_DATA, PAUSED +from h11._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from h11._state import ( + CLIENT, + CLOSED, + DONE, + ERROR, + IDLE, + MIGHT_SWITCH_PROTOCOL, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, + SWITCHED_PROTOCOL, +) +from h11._util import LocalProtocolError, ProtocolError, RemoteProtocolError +from h11._version import __version__ + +PRODUCT_ID = "python-h11/" + __version__ + + +__all__ = ( + "Connection", + "NEED_DATA", + "PAUSED", + "ConnectionClosed", + "Data", + "EndOfMessage", + "Event", + "InformationalResponse", + "Request", + "Response", + "CLIENT", + "CLOSED", + "DONE", + "ERROR", + "IDLE", + "MUST_CLOSE", + "SEND_BODY", + "SEND_RESPONSE", + "SERVER", + "SWITCHED_PROTOCOL", + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", +) diff --git a/.venv/Lib/site-packages/h11/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/h11/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cbaaf61eeb3ebbdd752638fd9f06990479d2987 GIT binary patch literal 1091 zcma))&uiN-6vyrSuoEY4el<;-u3HXeEQhxAxH1M=?5=?%4tBCJ%^z*3v^@r%7(J(ZOH>fBf{G^z@b9`>Lvnz{cm{&f!H;5H9#+ zK4Q1V^(SV05JtjCbOkH|5ywCb$3a}=zL=Z9Nsz=6kgyD7oB}DF25GDS1!q7eS`&A( zSOqFx01G$=aySq2xBv>c2#UA_N_Y`0;xZ`XC9s4mpc3s#xXXA2tl%oB;#II3`I7EE zyav{oEupnDrV892$>70hs-FJA#n$6gY5JarhT+)vXu7NG8q^y729mdG>>lO|ePTc_nt>-ksM&D=}ZK`wzRv$*& zXxh@d8hmDGuc#WCrf&6&uBB7S(#;pTNeegI+0Et&*tLsof9SZqzz_YQ@9II|2UNW~ zEtv@WA-dzNi!=k_6a|D`5W=&}x|Ku9s$`$n6UTtK6lqx@d!DAGS0qL+H3JgkOYf z77~6QGSW&*<%$E=v;(>bmpt15^c@my$s6V|`0)sL^w)kp7?OKCY1doKs; GEaNXPqZ^6< literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/h11/__pycache__/_abnf.cpython-312.pyc b/.venv/Lib/site-packages/h11/__pycache__/_abnf.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09c4fdc6857d2a2c48329a5becde2ccdd2d45320 GIT binary patch literal 1797 zcma)6O>E;-5PnYV)cJAZq^0d{S;*3b)+uSzUC|Yxw3O}kM?$4i7ZzM+jT^r&Y|k&G*fl`Nr~} z92Z0I{9G~uu57}Hik+$>vFAAaWkcvd4QX+*ZF{FmXuo@O=Op8nisq}W@f^=oo zOcjL??m2EB{X~6Di7cR*uF+9IN4iGGpt3kHdb(yLR957G5$&3hRAXWa81b$dJfIU@ zqthxUW`L3Gn$fGK#6Do~T{B)fObfkMWIZdHrJOaip1(bO=k+&6a%1C9(as9$-H!MAD67?`V;A(P>_t0HPYOy;+iTq$`wVo$3|z1ezvje$3o7^ zjJA);THVBT(-vl}hoiHr(*eJ(?Bba=g7~&jY<~E7&Avi3l-m9!#NXJ053$&0`*v%1 z>CvOr3BswE=APWZx>42YGwaYvNgDT!V7*7;yV$H~>P$1RwvZ)oLwyp3x&u`5;F=%pfVB5;Fx^bq{9o|C`Uf9ivx^pOGIleDJn zO7qy*F||Eut*YYes;+J0vS~;Qieb*JE@vA?cA=&iJJOO;-obiyUfb5B@dmCpqz|ea zl2J8reh)&*7B-~Hl9*=r>C+uZV zbi`pi7A1R~nD;(~q5}?d-j_W=>GR~PPW+soJK>9G{M;#7E59%Pu?)uHfRP$INe-VS zbEjn8KYD!pDHtd2u{6`?A)4V{K-Ru5KW;^7=5~uk1Fw8r^?fglD9*csVkswbo*w>Y z=qx>aN><^AtskpkoaA}xEhpj8G;@n&>W54Nx1Mbhb`COSN4`Jw{UFm#l)6bOojTzs z&iJWQvL5_8@Y@YAP$|ta1rM=|@PbFV!cl)K%`)${cyzD$i{SetOTrsOu|X%|B~UVV z!o7CJ38!RD9W5R&g8^|8i0gHt9vxx^JrrWn|E33?^|u(BiL@dpp7}~>^$?#lr{4gv F{|7l7^=JS9 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/h11/__pycache__/_connection.cpython-312.pyc b/.venv/Lib/site-packages/h11/__pycache__/_connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..844c45b8400efd786a84216e58ce9eae18b48bf9 GIT binary patch literal 23147 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/h11/__pycache__/_events.cpython-312.pyc b/.venv/Lib/site-packages/h11/__pycache__/_events.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15ba43a2649a1935890ab8368a32688c638962d2 GIT binary patch literal 13237 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/h11/__pycache__/_headers.cpython-312.pyc b/.venv/Lib/site-packages/h11/__pycache__/_headers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..751114412a498e3141dbfb87c56249cc28680b19 GIT binary patch literal 7999 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/h11/__pycache__/_version.cpython-312.pyc b/.venv/Lib/site-packages/h11/__pycache__/_version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..055c14c44d6fa89950731383c2a26f11d80b99f0 GIT binary patch literal 229 zcmX@j%ge<81j~}GGS)FMFgylvV1N4 zA77SQRGgWg7azZp;WJ3XuXJatn9yRNXiRWoZj4K6adt_5K}>LFN~%IYQGQlxa!GNF zTVipEV}PeZS+Rm!PGWI(j7MT}c50D=OMX^QRXW=X1U0Z=M2J+(L{ x!_ZJ4W}RL^6Nc*trn^7WF>eHTc$9C*AQBk#5 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" + r":" + r"{OWS}" + r"(?P{field_value})" + r"{OWS}".format(**globals()) +) + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#request.line +# +# request-line = method SP request-target SP HTTP-version CRLF +# method = token +# HTTP-version = HTTP-name "/" DIGIT "." DIGIT +# HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive +# +# request-target is complicated (see RFC 7230 sec 5.3) -- could be path, full +# URL, host+port (for connect), or even "*", but in any case we are guaranteed +# that it contists of the visible printing characters. +method = token +request_target = r"{vchar}+".format(**globals()) +http_version = r"HTTP/(?P[0-9]\.[0-9])" +request_line = ( + r"(?P{method})" + r" " + r"(?P{request_target})" + r" " + r"{http_version}".format(**globals()) +) + +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#status.line +# +# status-line = HTTP-version SP status-code SP reason-phrase CRLF +# status-code = 3DIGIT +# reason-phrase = *( HTAB / SP / VCHAR / obs-text ) +status_code = r"[0-9]{3}" +reason_phrase = r"([ \t]|{vchar_or_obs_text})*".format(**globals()) +status_line = ( + r"{http_version}" + r" " + r"(?P{status_code})" + # However, there are apparently a few too many servers out there that just + # leave out the reason phrase: + # https://github.com/scrapy/scrapy/issues/345#issuecomment-281756036 + # https://github.com/seanmonstar/httparse/issues/29 + # so make it optional. ?: is a non-capturing group. + r"(?: (?P{reason_phrase}))?".format(**globals()) +) + +HEXDIG = r"[0-9A-Fa-f]" +# Actually +# +# chunk-size = 1*HEXDIG +# +# but we impose an upper-limit to avoid ridiculosity. len(str(2**64)) == 20 +chunk_size = r"({HEXDIG}){{1,20}}".format(**globals()) +# Actually +# +# chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) +# +# but we aren't parsing the things so we don't really care. +chunk_ext = r";.*" +chunk_header = ( + r"(?P{chunk_size})" + r"(?P{chunk_ext})?" + r"{OWS}\r\n".format( + **globals() + ) # Even though the specification does not allow for extra whitespaces, + # we are lenient with trailing whitespaces because some servers on the wild use it. +) diff --git a/.venv/Lib/site-packages/h11/_connection.py b/.venv/Lib/site-packages/h11/_connection.py new file mode 100644 index 0000000..e37d82a --- /dev/null +++ b/.venv/Lib/site-packages/h11/_connection.py @@ -0,0 +1,659 @@ +# This contains the main Connection class. Everything in h11 revolves around +# this. +from typing import ( + Any, + Callable, + cast, + Dict, + List, + Optional, + overload, + Tuple, + Type, + Union, +) + +from ._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from ._headers import get_comma_header, has_expect_100_continue, set_comma_header +from ._readers import READERS, ReadersType +from ._receivebuffer import ReceiveBuffer +from ._state import ( + _SWITCH_CONNECT, + _SWITCH_UPGRADE, + CLIENT, + ConnectionState, + DONE, + ERROR, + MIGHT_SWITCH_PROTOCOL, + SEND_BODY, + SERVER, + SWITCHED_PROTOCOL, +) +from ._util import ( # Import the internal things we need + LocalProtocolError, + RemoteProtocolError, + Sentinel, +) +from ._writers import WRITERS, WritersType + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = ["Connection", "NEED_DATA", "PAUSED"] + + +class NEED_DATA(Sentinel, metaclass=Sentinel): + pass + + +class PAUSED(Sentinel, metaclass=Sentinel): + pass + + +# If we ever have this much buffered without it making a complete parseable +# event, we error out. The only time we really buffer is when reading the +# request/response line + headers together, so this is effectively the limit on +# the size of that. +# +# Some precedents for defaults: +# - node.js: 80 * 1024 +# - tomcat: 8 * 1024 +# - IIS: 16 * 1024 +# - Apache: <8 KiB per line> +DEFAULT_MAX_INCOMPLETE_EVENT_SIZE = 16 * 1024 + + +# RFC 7230's rules for connection lifecycles: +# - If either side says they want to close the connection, then the connection +# must close. +# - HTTP/1.1 defaults to keep-alive unless someone says Connection: close +# - HTTP/1.0 defaults to close unless both sides say Connection: keep-alive +# (and even this is a mess -- e.g. if you're implementing a proxy then +# sending Connection: keep-alive is forbidden). +# +# We simplify life by simply not supporting keep-alive with HTTP/1.0 peers. So +# our rule is: +# - If someone says Connection: close, we will close +# - If someone uses HTTP/1.0, we will close. +def _keep_alive(event: Union[Request, Response]) -> bool: + connection = get_comma_header(event.headers, b"connection") + if b"close" in connection: + return False + if getattr(event, "http_version", b"1.1") < b"1.1": + return False + return True + + +def _body_framing( + request_method: bytes, event: Union[Request, Response] +) -> Tuple[str, Union[Tuple[()], Tuple[int]]]: + # Called when we enter SEND_BODY to figure out framing information for + # this body. + # + # These are the only two events that can trigger a SEND_BODY state: + assert type(event) in (Request, Response) + # Returns one of: + # + # ("content-length", count) + # ("chunked", ()) + # ("http/1.0", ()) + # + # which are (lookup key, *args) for constructing body reader/writer + # objects. + # + # Reference: https://tools.ietf.org/html/rfc7230#section-3.3.3 + # + # Step 1: some responses always have an empty body, regardless of what the + # headers say. + if type(event) is Response: + if ( + event.status_code in (204, 304) + or request_method == b"HEAD" + or (request_method == b"CONNECT" and 200 <= event.status_code < 300) + ): + return ("content-length", (0,)) + # Section 3.3.3 also lists another case -- responses with status_code + # < 200. For us these are InformationalResponses, not Responses, so + # they can't get into this function in the first place. + assert event.status_code >= 200 + + # Step 2: check for Transfer-Encoding (T-E beats C-L): + transfer_encodings = get_comma_header(event.headers, b"transfer-encoding") + if transfer_encodings: + assert transfer_encodings == [b"chunked"] + return ("chunked", ()) + + # Step 3: check for Content-Length + content_lengths = get_comma_header(event.headers, b"content-length") + if content_lengths: + return ("content-length", (int(content_lengths[0]),)) + + # Step 4: no applicable headers; fallback/default depends on type + if type(event) is Request: + return ("content-length", (0,)) + else: + return ("http/1.0", ()) + + +################################################################ +# +# The main Connection class +# +################################################################ + + +class Connection: + """An object encapsulating the state of an HTTP connection. + + Args: + our_role: If you're implementing a client, pass :data:`h11.CLIENT`. If + you're implementing a server, pass :data:`h11.SERVER`. + + max_incomplete_event_size (int): + The maximum number of bytes we're willing to buffer of an + incomplete event. In practice this mostly sets a limit on the + maximum size of the request/response line + headers. If this is + exceeded, then :meth:`next_event` will raise + :exc:`RemoteProtocolError`. + + """ + + def __init__( + self, + our_role: Type[Sentinel], + max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, + ) -> None: + self._max_incomplete_event_size = max_incomplete_event_size + # State and role tracking + if our_role not in (CLIENT, SERVER): + raise ValueError(f"expected CLIENT or SERVER, not {our_role!r}") + self.our_role = our_role + self.their_role: Type[Sentinel] + if our_role is CLIENT: + self.their_role = SERVER + else: + self.their_role = CLIENT + self._cstate = ConnectionState() + + # Callables for converting data->events or vice-versa given the + # current state + self._writer = self._get_io_object(self.our_role, None, WRITERS) + self._reader = self._get_io_object(self.their_role, None, READERS) + + # Holds any unprocessed received data + self._receive_buffer = ReceiveBuffer() + # If this is true, then it indicates that the incoming connection was + # closed *after* the end of whatever's in self._receive_buffer: + self._receive_buffer_closed = False + + # Extra bits of state that don't fit into the state machine. + # + # These two are only used to interpret framing headers for figuring + # out how to read/write response bodies. their_http_version is also + # made available as a convenient public API. + self.their_http_version: Optional[bytes] = None + self._request_method: Optional[bytes] = None + # This is pure flow-control and doesn't at all affect the set of legal + # transitions, so no need to bother ConnectionState with it: + self.client_is_waiting_for_100_continue = False + + @property + def states(self) -> Dict[Type[Sentinel], Type[Sentinel]]: + """A dictionary like:: + + {CLIENT: , SERVER: } + + See :ref:`state-machine` for details. + + """ + return dict(self._cstate.states) + + @property + def our_state(self) -> Type[Sentinel]: + """The current state of whichever role we are playing. See + :ref:`state-machine` for details. + """ + return self._cstate.states[self.our_role] + + @property + def their_state(self) -> Type[Sentinel]: + """The current state of whichever role we are NOT playing. See + :ref:`state-machine` for details. + """ + return self._cstate.states[self.their_role] + + @property + def they_are_waiting_for_100_continue(self) -> bool: + return self.their_role is CLIENT and self.client_is_waiting_for_100_continue + + def start_next_cycle(self) -> None: + """Attempt to reset our connection state for a new request/response + cycle. + + If both client and server are in :data:`DONE` state, then resets them + both to :data:`IDLE` state in preparation for a new request/response + cycle on this same connection. Otherwise, raises a + :exc:`LocalProtocolError`. + + See :ref:`keepalive-and-pipelining`. + + """ + old_states = dict(self._cstate.states) + self._cstate.start_next_cycle() + self._request_method = None + # self.their_http_version gets left alone, since it presumably lasts + # beyond a single request/response cycle + assert not self.client_is_waiting_for_100_continue + self._respond_to_state_changes(old_states) + + def _process_error(self, role: Type[Sentinel]) -> None: + old_states = dict(self._cstate.states) + self._cstate.process_error(role) + self._respond_to_state_changes(old_states) + + def _server_switch_event(self, event: Event) -> Optional[Type[Sentinel]]: + if type(event) is InformationalResponse and event.status_code == 101: + return _SWITCH_UPGRADE + if type(event) is Response: + if ( + _SWITCH_CONNECT in self._cstate.pending_switch_proposals + and 200 <= event.status_code < 300 + ): + return _SWITCH_CONNECT + return None + + # All events go through here + def _process_event(self, role: Type[Sentinel], event: Event) -> None: + # First, pass the event through the state machine to make sure it + # succeeds. + old_states = dict(self._cstate.states) + if role is CLIENT and type(event) is Request: + if event.method == b"CONNECT": + self._cstate.process_client_switch_proposal(_SWITCH_CONNECT) + if get_comma_header(event.headers, b"upgrade"): + self._cstate.process_client_switch_proposal(_SWITCH_UPGRADE) + server_switch_event = None + if role is SERVER: + server_switch_event = self._server_switch_event(event) + self._cstate.process_event(role, type(event), server_switch_event) + + # Then perform the updates triggered by it. + + if type(event) is Request: + self._request_method = event.method + + if role is self.their_role and type(event) in ( + Request, + Response, + InformationalResponse, + ): + event = cast(Union[Request, Response, InformationalResponse], event) + self.their_http_version = event.http_version + + # Keep alive handling + # + # RFC 7230 doesn't really say what one should do if Connection: close + # shows up on a 1xx InformationalResponse. I think the idea is that + # this is not supposed to happen. In any case, if it does happen, we + # ignore it. + if type(event) in (Request, Response) and not _keep_alive( + cast(Union[Request, Response], event) + ): + self._cstate.process_keep_alive_disabled() + + # 100-continue + if type(event) is Request and has_expect_100_continue(event): + self.client_is_waiting_for_100_continue = True + if type(event) in (InformationalResponse, Response): + self.client_is_waiting_for_100_continue = False + if role is CLIENT and type(event) in (Data, EndOfMessage): + self.client_is_waiting_for_100_continue = False + + self._respond_to_state_changes(old_states, event) + + def _get_io_object( + self, + role: Type[Sentinel], + event: Optional[Event], + io_dict: Union[ReadersType, WritersType], + ) -> Optional[Callable[..., Any]]: + # event may be None; it's only used when entering SEND_BODY + state = self._cstate.states[role] + if state is SEND_BODY: + # Special case: the io_dict has a dict of reader/writer factories + # that depend on the request/response framing. + framing_type, args = _body_framing( + cast(bytes, self._request_method), cast(Union[Request, Response], event) + ) + return io_dict[SEND_BODY][framing_type](*args) # type: ignore[index] + else: + # General case: the io_dict just has the appropriate reader/writer + # for this state + return io_dict.get((role, state)) # type: ignore[return-value] + + # This must be called after any action that might have caused + # self._cstate.states to change. + def _respond_to_state_changes( + self, + old_states: Dict[Type[Sentinel], Type[Sentinel]], + event: Optional[Event] = None, + ) -> None: + # Update reader/writer + if self.our_state != old_states[self.our_role]: + self._writer = self._get_io_object(self.our_role, event, WRITERS) + if self.their_state != old_states[self.their_role]: + self._reader = self._get_io_object(self.their_role, event, READERS) + + @property + def trailing_data(self) -> Tuple[bytes, bool]: + """Data that has been received, but not yet processed, represented as + a tuple with two elements, where the first is a byte-string containing + the unprocessed data itself, and the second is a bool that is True if + the receive connection was closed. + + See :ref:`switching-protocols` for discussion of why you'd want this. + """ + return (bytes(self._receive_buffer), self._receive_buffer_closed) + + def receive_data(self, data: bytes) -> None: + """Add data to our internal receive buffer. + + This does not actually do any processing on the data, just stores + it. To trigger processing, you have to call :meth:`next_event`. + + Args: + data (:term:`bytes-like object`): + The new data that was just received. + + Special case: If *data* is an empty byte-string like ``b""``, + then this indicates that the remote side has closed the + connection (end of file). Normally this is convenient, because + standard Python APIs like :meth:`file.read` or + :meth:`socket.recv` use ``b""`` to indicate end-of-file, while + other failures to read are indicated using other mechanisms + like raising :exc:`TimeoutError`. When using such an API you + can just blindly pass through whatever you get from ``read`` + to :meth:`receive_data`, and everything will work. + + But, if you have an API where reading an empty string is a + valid non-EOF condition, then you need to be aware of this and + make sure to check for such strings and avoid passing them to + :meth:`receive_data`. + + Returns: + Nothing, but after calling this you should call :meth:`next_event` + to parse the newly received data. + + Raises: + RuntimeError: + Raised if you pass an empty *data*, indicating EOF, and then + pass a non-empty *data*, indicating more data that somehow + arrived after the EOF. + + (Calling ``receive_data(b"")`` multiple times is fine, + and equivalent to calling it once.) + + """ + if data: + if self._receive_buffer_closed: + raise RuntimeError("received close, then received more data?") + self._receive_buffer += data + else: + self._receive_buffer_closed = True + + def _extract_next_receive_event( + self, + ) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: + state = self.their_state + # We don't pause immediately when they enter DONE, because even in + # DONE state we can still process a ConnectionClosed() event. But + # if we have data in our buffer, then we definitely aren't getting + # a ConnectionClosed() immediately and we need to pause. + if state is DONE and self._receive_buffer: + return PAUSED + if state is MIGHT_SWITCH_PROTOCOL or state is SWITCHED_PROTOCOL: + return PAUSED + assert self._reader is not None + event = self._reader(self._receive_buffer) + if event is None: + if not self._receive_buffer and self._receive_buffer_closed: + # In some unusual cases (basically just HTTP/1.0 bodies), EOF + # triggers an actual protocol event; in that case, we want to + # return that event, and then the state will change and we'll + # get called again to generate the actual ConnectionClosed(). + if hasattr(self._reader, "read_eof"): + event = self._reader.read_eof() + else: + event = ConnectionClosed() + if event is None: + event = NEED_DATA + return event # type: ignore[no-any-return] + + def next_event(self) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: + """Parse the next event out of our receive buffer, update our internal + state, and return it. + + This is a mutating operation -- think of it like calling :func:`next` + on an iterator. + + Returns: + : One of three things: + + 1) An event object -- see :ref:`events`. + + 2) The special constant :data:`NEED_DATA`, which indicates that + you need to read more data from your socket and pass it to + :meth:`receive_data` before this method will be able to return + any more events. + + 3) The special constant :data:`PAUSED`, which indicates that we + are not in a state where we can process incoming data (usually + because the peer has finished their part of the current + request/response cycle, and you have not yet called + :meth:`start_next_cycle`). See :ref:`flow-control` for details. + + Raises: + RemoteProtocolError: + The peer has misbehaved. You should close the connection + (possibly after sending some kind of 4xx response). + + Once this method returns :class:`ConnectionClosed` once, then all + subsequent calls will also return :class:`ConnectionClosed`. + + If this method raises any exception besides :exc:`RemoteProtocolError` + then that's a bug -- if it happens please file a bug report! + + If this method raises any exception then it also sets + :attr:`Connection.their_state` to :data:`ERROR` -- see + :ref:`error-handling` for discussion. + + """ + + if self.their_state is ERROR: + raise RemoteProtocolError("Can't receive data when peer state is ERROR") + try: + event = self._extract_next_receive_event() + if event not in [NEED_DATA, PAUSED]: + self._process_event(self.their_role, cast(Event, event)) + if event is NEED_DATA: + if len(self._receive_buffer) > self._max_incomplete_event_size: + # 431 is "Request header fields too large" which is pretty + # much the only situation where we can get here + raise RemoteProtocolError( + "Receive buffer too long", error_status_hint=431 + ) + if self._receive_buffer_closed: + # We're still trying to complete some event, but that's + # never going to happen because no more data is coming + raise RemoteProtocolError("peer unexpectedly closed connection") + return event + except BaseException as exc: + self._process_error(self.their_role) + if isinstance(exc, LocalProtocolError): + exc._reraise_as_remote_protocol_error() + else: + raise + + @overload + def send(self, event: ConnectionClosed) -> None: + ... + + @overload + def send( + self, event: Union[Request, InformationalResponse, Response, Data, EndOfMessage] + ) -> bytes: + ... + + @overload + def send(self, event: Event) -> Optional[bytes]: + ... + + def send(self, event: Event) -> Optional[bytes]: + """Convert a high-level event into bytes that can be sent to the peer, + while updating our internal state machine. + + Args: + event: The :ref:`event ` to send. + + Returns: + If ``type(event) is ConnectionClosed``, then returns + ``None``. Otherwise, returns a :term:`bytes-like object`. + + Raises: + LocalProtocolError: + Sending this event at this time would violate our + understanding of the HTTP/1.1 protocol. + + If this method raises any exception then it also sets + :attr:`Connection.our_state` to :data:`ERROR` -- see + :ref:`error-handling` for discussion. + + """ + data_list = self.send_with_data_passthrough(event) + if data_list is None: + return None + else: + return b"".join(data_list) + + def send_with_data_passthrough(self, event: Event) -> Optional[List[bytes]]: + """Identical to :meth:`send`, except that in situations where + :meth:`send` returns a single :term:`bytes-like object`, this instead + returns a list of them -- and when sending a :class:`Data` event, this + list is guaranteed to contain the exact object you passed in as + :attr:`Data.data`. See :ref:`sendfile` for discussion. + + """ + if self.our_state is ERROR: + raise LocalProtocolError("Can't send data when our state is ERROR") + try: + if type(event) is Response: + event = self._clean_up_response_headers_for_sending(event) + # We want to call _process_event before calling the writer, + # because if someone tries to do something invalid then this will + # give a sensible error message, while our writers all just assume + # they will only receive valid events. But, _process_event might + # change self._writer. So we have to do a little dance: + writer = self._writer + self._process_event(self.our_role, event) + if type(event) is ConnectionClosed: + return None + else: + # In any situation where writer is None, process_event should + # have raised ProtocolError + assert writer is not None + data_list: List[bytes] = [] + writer(event, data_list.append) + return data_list + except: + self._process_error(self.our_role) + raise + + def send_failed(self) -> None: + """Notify the state machine that we failed to send the data it gave + us. + + This causes :attr:`Connection.our_state` to immediately become + :data:`ERROR` -- see :ref:`error-handling` for discussion. + + """ + self._process_error(self.our_role) + + # When sending a Response, we take responsibility for a few things: + # + # - Sometimes you MUST set Connection: close. We take care of those + # times. (You can also set it yourself if you want, and if you do then + # we'll respect that and close the connection at the right time. But you + # don't have to worry about that unless you want to.) + # + # - The user has to set Content-Length if they want it. Otherwise, for + # responses that have bodies (e.g. not HEAD), then we will automatically + # select the right mechanism for streaming a body of unknown length, + # which depends on depending on the peer's HTTP version. + # + # This function's *only* responsibility is making sure headers are set up + # right -- everything downstream just looks at the headers. There are no + # side channels. + def _clean_up_response_headers_for_sending(self, response: Response) -> Response: + assert type(response) is Response + + headers = response.headers + need_close = False + + # HEAD requests need some special handling: they always act like they + # have Content-Length: 0, and that's how _body_framing treats + # them. But their headers are supposed to match what we would send if + # the request was a GET. (Technically there is one deviation allowed: + # we're allowed to leave out the framing headers -- see + # https://tools.ietf.org/html/rfc7231#section-4.3.2 . But it's just as + # easy to get them right.) + method_for_choosing_headers = cast(bytes, self._request_method) + if method_for_choosing_headers == b"HEAD": + method_for_choosing_headers = b"GET" + framing_type, _ = _body_framing(method_for_choosing_headers, response) + if framing_type in ("chunked", "http/1.0"): + # This response has a body of unknown length. + # If our peer is HTTP/1.1, we use Transfer-Encoding: chunked + # If our peer is HTTP/1.0, we use no framing headers, and close the + # connection afterwards. + # + # Make sure to clear Content-Length (in principle user could have + # set both and then we ignored Content-Length b/c + # Transfer-Encoding overwrote it -- this would be naughty of them, + # but the HTTP spec says that if our peer does this then we have + # to fix it instead of erroring out, so we'll accord the user the + # same respect). + headers = set_comma_header(headers, b"content-length", []) + if self.their_http_version is None or self.their_http_version < b"1.1": + # Either we never got a valid request and are sending back an + # error (their_http_version is None), so we assume the worst; + # or else we did get a valid HTTP/1.0 request, so we know that + # they don't understand chunked encoding. + headers = set_comma_header(headers, b"transfer-encoding", []) + # This is actually redundant ATM, since currently we + # unconditionally disable keep-alive when talking to HTTP/1.0 + # peers. But let's be defensive just in case we add + # Connection: keep-alive support later: + if self._request_method != b"HEAD": + need_close = True + else: + headers = set_comma_header(headers, b"transfer-encoding", [b"chunked"]) + + if not self._cstate.keep_alive or need_close: + # Make sure Connection: close is set + connection = set(get_comma_header(headers, b"connection")) + connection.discard(b"keep-alive") + connection.add(b"close") + headers = set_comma_header(headers, b"connection", sorted(connection)) + + return Response( + headers=headers, + status_code=response.status_code, + http_version=response.http_version, + reason=response.reason, + ) diff --git a/.venv/Lib/site-packages/h11/_events.py b/.venv/Lib/site-packages/h11/_events.py new file mode 100644 index 0000000..ca1c3ad --- /dev/null +++ b/.venv/Lib/site-packages/h11/_events.py @@ -0,0 +1,369 @@ +# High level events that make up HTTP/1.1 conversations. Loosely inspired by +# the corresponding events in hyper-h2: +# +# http://python-hyper.org/h2/en/stable/api.html#events +# +# Don't subclass these. Stuff will break. + +import re +from abc import ABC +from dataclasses import dataclass +from typing import List, Tuple, Union + +from ._abnf import method, request_target +from ._headers import Headers, normalize_and_validate +from ._util import bytesify, LocalProtocolError, validate + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = [ + "Event", + "Request", + "InformationalResponse", + "Response", + "Data", + "EndOfMessage", + "ConnectionClosed", +] + +method_re = re.compile(method.encode("ascii")) +request_target_re = re.compile(request_target.encode("ascii")) + + +class Event(ABC): + """ + Base class for h11 events. + """ + + __slots__ = () + + +@dataclass(init=False, frozen=True) +class Request(Event): + """The beginning of an HTTP request. + + Fields: + + .. attribute:: method + + An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte + string. :term:`Bytes-like objects ` and native + strings containing only ascii characters will be automatically + converted to byte strings. + + .. attribute:: target + + The target of an HTTP request, e.g. ``b"/index.html"``, or one of the + more exotic formats described in `RFC 7320, section 5.3 + `_. Always a byte + string. :term:`Bytes-like objects ` and native + strings containing only ascii characters will be automatically + converted to byte strings. + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + """ + + __slots__ = ("method", "headers", "target", "http_version") + + method: bytes + headers: Headers + target: bytes + http_version: bytes + + def __init__( + self, + *, + method: Union[bytes, str], + headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], + target: Union[bytes, str], + http_version: Union[bytes, str] = b"1.1", + _parsed: bool = False, + ) -> None: + super().__init__() + if isinstance(headers, Headers): + object.__setattr__(self, "headers", headers) + else: + object.__setattr__( + self, "headers", normalize_and_validate(headers, _parsed=_parsed) + ) + if not _parsed: + object.__setattr__(self, "method", bytesify(method)) + object.__setattr__(self, "target", bytesify(target)) + object.__setattr__(self, "http_version", bytesify(http_version)) + else: + object.__setattr__(self, "method", method) + object.__setattr__(self, "target", target) + object.__setattr__(self, "http_version", http_version) + + # "A server MUST respond with a 400 (Bad Request) status code to any + # HTTP/1.1 request message that lacks a Host header field and to any + # request message that contains more than one Host header field or a + # Host header field with an invalid field-value." + # -- https://tools.ietf.org/html/rfc7230#section-5.4 + host_count = 0 + for name, value in self.headers: + if name == b"host": + host_count += 1 + if self.http_version == b"1.1" and host_count == 0: + raise LocalProtocolError("Missing mandatory Host: header") + if host_count > 1: + raise LocalProtocolError("Found multiple Host: headers") + + validate(method_re, self.method, "Illegal method characters") + validate(request_target_re, self.target, "Illegal target characters") + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class _ResponseBase(Event): + __slots__ = ("headers", "http_version", "reason", "status_code") + + headers: Headers + http_version: bytes + reason: bytes + status_code: int + + def __init__( + self, + *, + headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], + status_code: int, + http_version: Union[bytes, str] = b"1.1", + reason: Union[bytes, str] = b"", + _parsed: bool = False, + ) -> None: + super().__init__() + if isinstance(headers, Headers): + object.__setattr__(self, "headers", headers) + else: + object.__setattr__( + self, "headers", normalize_and_validate(headers, _parsed=_parsed) + ) + if not _parsed: + object.__setattr__(self, "reason", bytesify(reason)) + object.__setattr__(self, "http_version", bytesify(http_version)) + if not isinstance(status_code, int): + raise LocalProtocolError("status code must be integer") + # Because IntEnum objects are instances of int, but aren't + # duck-compatible (sigh), see gh-72. + object.__setattr__(self, "status_code", int(status_code)) + else: + object.__setattr__(self, "reason", reason) + object.__setattr__(self, "http_version", http_version) + object.__setattr__(self, "status_code", status_code) + + self.__post_init__() + + def __post_init__(self) -> None: + pass + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class InformationalResponse(_ResponseBase): + """An HTTP informational response. + + Fields: + + .. attribute:: status_code + + The status code of this response, as an integer. For an + :class:`InformationalResponse`, this is always in the range [100, + 200). + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for + details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + .. attribute:: reason + + The reason phrase of this response, as a byte string. For example: + ``b"OK"``, or ``b"Not Found"``. + + """ + + def __post_init__(self) -> None: + if not (100 <= self.status_code < 200): + raise LocalProtocolError( + "InformationalResponse status_code should be in range " + "[100, 200), not {}".format(self.status_code) + ) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class Response(_ResponseBase): + """The beginning of an HTTP response. + + Fields: + + .. attribute:: status_code + + The status code of this response, as an integer. For an + :class:`Response`, this is always in the range [200, + 1000). + + .. attribute:: headers + + Request headers, represented as a list of (name, value) pairs. See + :ref:`the header normalization rules ` for details. + + .. attribute:: http_version + + The HTTP protocol version, represented as a byte string like + ``b"1.1"``. See :ref:`the HTTP version normalization rules + ` for details. + + .. attribute:: reason + + The reason phrase of this response, as a byte string. For example: + ``b"OK"``, or ``b"Not Found"``. + + """ + + def __post_init__(self) -> None: + if not (200 <= self.status_code < 1000): + raise LocalProtocolError( + "Response status_code should be in range [200, 1000), not {}".format( + self.status_code + ) + ) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(init=False, frozen=True) +class Data(Event): + """Part of an HTTP message body. + + Fields: + + .. attribute:: data + + A :term:`bytes-like object` containing part of a message body. Or, if + using the ``combine=False`` argument to :meth:`Connection.send`, then + any object that your socket writing code knows what to do with, and for + which calling :func:`len` returns the number of bytes that will be + written -- see :ref:`sendfile` for details. + + .. attribute:: chunk_start + + A marker that indicates whether this data object is from the start of a + chunked transfer encoding chunk. This field is ignored when when a Data + event is provided to :meth:`Connection.send`: it is only valid on + events emitted from :meth:`Connection.next_event`. You probably + shouldn't use this attribute at all; see + :ref:`chunk-delimiters-are-bad` for details. + + .. attribute:: chunk_end + + A marker that indicates whether this data object is the last for a + given chunked transfer encoding chunk. This field is ignored when when + a Data event is provided to :meth:`Connection.send`: it is only valid + on events emitted from :meth:`Connection.next_event`. You probably + shouldn't use this attribute at all; see + :ref:`chunk-delimiters-are-bad` for details. + + """ + + __slots__ = ("data", "chunk_start", "chunk_end") + + data: bytes + chunk_start: bool + chunk_end: bool + + def __init__( + self, data: bytes, chunk_start: bool = False, chunk_end: bool = False + ) -> None: + object.__setattr__(self, "data", data) + object.__setattr__(self, "chunk_start", chunk_start) + object.__setattr__(self, "chunk_end", chunk_end) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that +# are forbidden to be sent in a trailer, since processing them as if they were +# present in the header section might bypass external security filters." +# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part +# Unfortunately, the list of forbidden fields is long and vague :-/ +@dataclass(init=False, frozen=True) +class EndOfMessage(Event): + """The end of an HTTP message. + + Fields: + + .. attribute:: headers + + Default value: ``[]`` + + Any trailing headers attached to this message, represented as a list of + (name, value) pairs. See :ref:`the header normalization rules + ` for details. + + Must be empty unless ``Transfer-Encoding: chunked`` is in use. + + """ + + __slots__ = ("headers",) + + headers: Headers + + def __init__( + self, + *, + headers: Union[ + Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None + ] = None, + _parsed: bool = False, + ) -> None: + super().__init__() + if headers is None: + headers = Headers([]) + elif not isinstance(headers, Headers): + headers = normalize_and_validate(headers, _parsed=_parsed) + + object.__setattr__(self, "headers", headers) + + # This is an unhashable type. + __hash__ = None # type: ignore + + +@dataclass(frozen=True) +class ConnectionClosed(Event): + """This event indicates that the sender has closed their outgoing + connection. + + Note that this does not necessarily mean that they can't *receive* further + data, because TCP connections are composed to two one-way channels which + can be closed independently. See :ref:`closing` for details. + + No fields. + """ + + pass diff --git a/.venv/Lib/site-packages/h11/_headers.py b/.venv/Lib/site-packages/h11/_headers.py new file mode 100644 index 0000000..31da3e2 --- /dev/null +++ b/.venv/Lib/site-packages/h11/_headers.py @@ -0,0 +1,282 @@ +import re +from typing import AnyStr, cast, List, overload, Sequence, Tuple, TYPE_CHECKING, Union + +from ._abnf import field_name, field_value +from ._util import bytesify, LocalProtocolError, validate + +if TYPE_CHECKING: + from ._events import Request + +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal # type: ignore + +CONTENT_LENGTH_MAX_DIGITS = 20 # allow up to 1 billion TB - 1 + + +# Facts +# ----- +# +# Headers are: +# keys: case-insensitive ascii +# values: mixture of ascii and raw bytes +# +# "Historically, HTTP has allowed field content with text in the ISO-8859-1 +# charset [ISO-8859-1], supporting other charsets only through use of +# [RFC2047] encoding. In practice, most HTTP header field values use only a +# subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD +# limit their field values to US-ASCII octets. A recipient SHOULD treat other +# octets in field content (obs-text) as opaque data." +# And it deprecates all non-ascii values +# +# Leading/trailing whitespace in header names is forbidden +# +# Values get leading/trailing whitespace stripped +# +# Content-Disposition actually needs to contain unicode semantically; to +# accomplish this it has a terrifically weird way of encoding the filename +# itself as ascii (and even this still has lots of cross-browser +# incompatibilities) +# +# Order is important: +# "a proxy MUST NOT change the order of these field values when forwarding a +# message" +# (and there are several headers where the order indicates a preference) +# +# Multiple occurences of the same header: +# "A sender MUST NOT generate multiple header fields with the same field name +# in a message unless either the entire field value for that header field is +# defined as a comma-separated list [or the header is Set-Cookie which gets a +# special exception]" - RFC 7230. (cookies are in RFC 6265) +# +# So every header aside from Set-Cookie can be merged by b", ".join if it +# occurs repeatedly. But, of course, they can't necessarily be split by +# .split(b","), because quoting. +# +# Given all this mess (case insensitive, duplicates allowed, order is +# important, ...), there doesn't appear to be any standard way to handle +# headers in Python -- they're almost like dicts, but... actually just +# aren't. For now we punt and just use a super simple representation: headers +# are a list of pairs +# +# [(name1, value1), (name2, value2), ...] +# +# where all entries are bytestrings, names are lowercase and have no +# leading/trailing whitespace, and values are bytestrings with no +# leading/trailing whitespace. Searching and updating are done via naive O(n) +# methods. +# +# Maybe a dict-of-lists would be better? + +_content_length_re = re.compile(rb"[0-9]+") +_field_name_re = re.compile(field_name.encode("ascii")) +_field_value_re = re.compile(field_value.encode("ascii")) + + +class Headers(Sequence[Tuple[bytes, bytes]]): + """ + A list-like interface that allows iterating over headers as byte-pairs + of (lowercased-name, value). + + Internally we actually store the representation as three-tuples, + including both the raw original casing, in order to preserve casing + over-the-wire, and the lowercased name, for case-insensitive comparisions. + + r = Request( + method="GET", + target="/", + headers=[("Host", "example.org"), ("Connection", "keep-alive")], + http_version="1.1", + ) + assert r.headers == [ + (b"host", b"example.org"), + (b"connection", b"keep-alive") + ] + assert r.headers.raw_items() == [ + (b"Host", b"example.org"), + (b"Connection", b"keep-alive") + ] + """ + + __slots__ = "_full_items" + + def __init__(self, full_items: List[Tuple[bytes, bytes, bytes]]) -> None: + self._full_items = full_items + + def __bool__(self) -> bool: + return bool(self._full_items) + + def __eq__(self, other: object) -> bool: + return list(self) == list(other) # type: ignore + + def __len__(self) -> int: + return len(self._full_items) + + def __repr__(self) -> str: + return "" % repr(list(self)) + + def __getitem__(self, idx: int) -> Tuple[bytes, bytes]: # type: ignore[override] + _, name, value = self._full_items[idx] + return (name, value) + + def raw_items(self) -> List[Tuple[bytes, bytes]]: + return [(raw_name, value) for raw_name, _, value in self._full_items] + + +HeaderTypes = Union[ + List[Tuple[bytes, bytes]], + List[Tuple[bytes, str]], + List[Tuple[str, bytes]], + List[Tuple[str, str]], +] + + +@overload +def normalize_and_validate(headers: Headers, _parsed: Literal[True]) -> Headers: + ... + + +@overload +def normalize_and_validate(headers: HeaderTypes, _parsed: Literal[False]) -> Headers: + ... + + +@overload +def normalize_and_validate( + headers: Union[Headers, HeaderTypes], _parsed: bool = False +) -> Headers: + ... + + +def normalize_and_validate( + headers: Union[Headers, HeaderTypes], _parsed: bool = False +) -> Headers: + new_headers = [] + seen_content_length = None + saw_transfer_encoding = False + for name, value in headers: + # For headers coming out of the parser, we can safely skip some steps, + # because it always returns bytes and has already run these regexes + # over the data: + if not _parsed: + name = bytesify(name) + value = bytesify(value) + validate(_field_name_re, name, "Illegal header name {!r}", name) + validate(_field_value_re, value, "Illegal header value {!r}", value) + assert isinstance(name, bytes) + assert isinstance(value, bytes) + + raw_name = name + name = name.lower() + if name == b"content-length": + lengths = {length.strip() for length in value.split(b",")} + if len(lengths) != 1: + raise LocalProtocolError("conflicting Content-Length headers") + value = lengths.pop() + validate(_content_length_re, value, "bad Content-Length") + if len(value) > CONTENT_LENGTH_MAX_DIGITS: + raise LocalProtocolError("bad Content-Length") + if seen_content_length is None: + seen_content_length = value + new_headers.append((raw_name, name, value)) + elif seen_content_length != value: + raise LocalProtocolError("conflicting Content-Length headers") + elif name == b"transfer-encoding": + # "A server that receives a request message with a transfer coding + # it does not understand SHOULD respond with 501 (Not + # Implemented)." + # https://tools.ietf.org/html/rfc7230#section-3.3.1 + if saw_transfer_encoding: + raise LocalProtocolError( + "multiple Transfer-Encoding headers", error_status_hint=501 + ) + # "All transfer-coding names are case-insensitive" + # -- https://tools.ietf.org/html/rfc7230#section-4 + value = value.lower() + if value != b"chunked": + raise LocalProtocolError( + "Only Transfer-Encoding: chunked is supported", + error_status_hint=501, + ) + saw_transfer_encoding = True + new_headers.append((raw_name, name, value)) + else: + new_headers.append((raw_name, name, value)) + return Headers(new_headers) + + +def get_comma_header(headers: Headers, name: bytes) -> List[bytes]: + # Should only be used for headers whose value is a list of + # comma-separated, case-insensitive values. + # + # The header name `name` is expected to be lower-case bytes. + # + # Connection: meets these criteria (including cast insensitivity). + # + # Content-Length: technically is just a single value (1*DIGIT), but the + # standard makes reference to implementations that do multiple values, and + # using this doesn't hurt. Ditto, case insensitivity doesn't things either + # way. + # + # Transfer-Encoding: is more complex (allows for quoted strings), so + # splitting on , is actually wrong. For example, this is legal: + # + # Transfer-Encoding: foo; options="1,2", chunked + # + # and should be parsed as + # + # foo; options="1,2" + # chunked + # + # but this naive function will parse it as + # + # foo; options="1 + # 2" + # chunked + # + # However, this is okay because the only thing we are going to do with + # any Transfer-Encoding is reject ones that aren't just "chunked", so + # both of these will be treated the same anyway. + # + # Expect: the only legal value is the literal string + # "100-continue". Splitting on commas is harmless. Case insensitive. + # + out: List[bytes] = [] + for _, found_name, found_raw_value in headers._full_items: + if found_name == name: + found_raw_value = found_raw_value.lower() + for found_split_value in found_raw_value.split(b","): + found_split_value = found_split_value.strip() + if found_split_value: + out.append(found_split_value) + return out + + +def set_comma_header(headers: Headers, name: bytes, new_values: List[bytes]) -> Headers: + # The header name `name` is expected to be lower-case bytes. + # + # Note that when we store the header we use title casing for the header + # names, in order to match the conventional HTTP header style. + # + # Simply calling `.title()` is a blunt approach, but it's correct + # here given the cases where we're using `set_comma_header`... + # + # Connection, Content-Length, Transfer-Encoding. + new_headers: List[Tuple[bytes, bytes]] = [] + for found_raw_name, found_name, found_raw_value in headers._full_items: + if found_name != name: + new_headers.append((found_raw_name, found_raw_value)) + for new_value in new_values: + new_headers.append((name.title(), new_value)) + return normalize_and_validate(new_headers) + + +def has_expect_100_continue(request: "Request") -> bool: + # https://tools.ietf.org/html/rfc7231#section-5.1.1 + # "A server that receives a 100-continue expectation in an HTTP/1.0 request + # MUST ignore that expectation." + if request.http_version < b"1.1": + return False + expect = get_comma_header(request.headers, b"expect") + return b"100-continue" in expect diff --git a/.venv/Lib/site-packages/h11/_readers.py b/.venv/Lib/site-packages/h11/_readers.py new file mode 100644 index 0000000..576804c --- /dev/null +++ b/.venv/Lib/site-packages/h11/_readers.py @@ -0,0 +1,250 @@ +# Code to read HTTP data +# +# Strategy: each reader is a callable which takes a ReceiveBuffer object, and +# either: +# 1) consumes some of it and returns an Event +# 2) raises a LocalProtocolError (for consistency -- e.g. we call validate() +# and it might raise a LocalProtocolError, so simpler just to always use +# this) +# 3) returns None, meaning "I need more data" +# +# If they have a .read_eof attribute, then this will be called if an EOF is +# received -- but this is optional. Either way, the actual ConnectionClosed +# event will be generated afterwards. +# +# READERS is a dict describing how to pick a reader. It maps states to either: +# - a reader +# - or, for body readers, a dict of per-framing reader factories + +import re +from typing import Any, Callable, Dict, Iterable, NoReturn, Optional, Tuple, Type, Union + +from ._abnf import chunk_header, header_field, request_line, status_line +from ._events import Data, EndOfMessage, InformationalResponse, Request, Response +from ._receivebuffer import ReceiveBuffer +from ._state import ( + CLIENT, + CLOSED, + DONE, + IDLE, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, +) +from ._util import LocalProtocolError, RemoteProtocolError, Sentinel, validate + +__all__ = ["READERS"] + +header_field_re = re.compile(header_field.encode("ascii")) +obs_fold_re = re.compile(rb"[ \t]+") + + +def _obsolete_line_fold(lines: Iterable[bytes]) -> Iterable[bytes]: + it = iter(lines) + last: Optional[bytes] = None + for line in it: + match = obs_fold_re.match(line) + if match: + if last is None: + raise LocalProtocolError("continuation line at start of headers") + if not isinstance(last, bytearray): + # Cast to a mutable type, avoiding copy on append to ensure O(n) time + last = bytearray(last) + last += b" " + last += line[match.end() :] + else: + if last is not None: + yield last + last = line + if last is not None: + yield last + + +def _decode_header_lines( + lines: Iterable[bytes], +) -> Iterable[Tuple[bytes, bytes]]: + for line in _obsolete_line_fold(lines): + matches = validate(header_field_re, line, "illegal header line: {!r}", line) + yield (matches["field_name"], matches["field_value"]) + + +request_line_re = re.compile(request_line.encode("ascii")) + + +def maybe_read_from_IDLE_client(buf: ReceiveBuffer) -> Optional[Request]: + lines = buf.maybe_extract_lines() + if lines is None: + if buf.is_next_line_obviously_invalid_request_line(): + raise LocalProtocolError("illegal request line") + return None + if not lines: + raise LocalProtocolError("no request line received") + matches = validate( + request_line_re, lines[0], "illegal request line: {!r}", lines[0] + ) + return Request( + headers=list(_decode_header_lines(lines[1:])), _parsed=True, **matches + ) + + +status_line_re = re.compile(status_line.encode("ascii")) + + +def maybe_read_from_SEND_RESPONSE_server( + buf: ReceiveBuffer, +) -> Union[InformationalResponse, Response, None]: + lines = buf.maybe_extract_lines() + if lines is None: + if buf.is_next_line_obviously_invalid_request_line(): + raise LocalProtocolError("illegal request line") + return None + if not lines: + raise LocalProtocolError("no response line received") + matches = validate(status_line_re, lines[0], "illegal status line: {!r}", lines[0]) + http_version = ( + b"1.1" if matches["http_version"] is None else matches["http_version"] + ) + reason = b"" if matches["reason"] is None else matches["reason"] + status_code = int(matches["status_code"]) + class_: Union[Type[InformationalResponse], Type[Response]] = ( + InformationalResponse if status_code < 200 else Response + ) + return class_( + headers=list(_decode_header_lines(lines[1:])), + _parsed=True, + status_code=status_code, + reason=reason, + http_version=http_version, + ) + + +class ContentLengthReader: + def __init__(self, length: int) -> None: + self._length = length + self._remaining = length + + def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: + if self._remaining == 0: + return EndOfMessage() + data = buf.maybe_extract_at_most(self._remaining) + if data is None: + return None + self._remaining -= len(data) + return Data(data=data) + + def read_eof(self) -> NoReturn: + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(received {} bytes, expected {})".format( + self._length - self._remaining, self._length + ) + ) + + +chunk_header_re = re.compile(chunk_header.encode("ascii")) + + +class ChunkedReader: + def __init__(self) -> None: + self._bytes_in_chunk = 0 + # After reading a chunk, we have to throw away the trailing \r\n. + # This tracks the bytes that we need to match and throw away. + self._bytes_to_discard = b"" + self._reading_trailer = False + + def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: + if self._reading_trailer: + lines = buf.maybe_extract_lines() + if lines is None: + return None + return EndOfMessage(headers=list(_decode_header_lines(lines))) + if self._bytes_to_discard: + data = buf.maybe_extract_at_most(len(self._bytes_to_discard)) + if data is None: + return None + if data != self._bytes_to_discard[: len(data)]: + raise LocalProtocolError( + f"malformed chunk footer: {data!r} (expected {self._bytes_to_discard!r})" + ) + self._bytes_to_discard = self._bytes_to_discard[len(data) :] + if self._bytes_to_discard: + return None + # else, fall through and read some more + assert self._bytes_to_discard == b"" + if self._bytes_in_chunk == 0: + # We need to refill our chunk count + chunk_header = buf.maybe_extract_next_line() + if chunk_header is None: + return None + matches = validate( + chunk_header_re, + chunk_header, + "illegal chunk header: {!r}", + chunk_header, + ) + # XX FIXME: we discard chunk extensions. Does anyone care? + self._bytes_in_chunk = int(matches["chunk_size"], base=16) + if self._bytes_in_chunk == 0: + self._reading_trailer = True + return self(buf) + chunk_start = True + else: + chunk_start = False + assert self._bytes_in_chunk > 0 + data = buf.maybe_extract_at_most(self._bytes_in_chunk) + if data is None: + return None + self._bytes_in_chunk -= len(data) + if self._bytes_in_chunk == 0: + self._bytes_to_discard = b"\r\n" + chunk_end = True + else: + chunk_end = False + return Data(data=data, chunk_start=chunk_start, chunk_end=chunk_end) + + def read_eof(self) -> NoReturn: + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(incomplete chunked read)" + ) + + +class Http10Reader: + def __call__(self, buf: ReceiveBuffer) -> Optional[Data]: + data = buf.maybe_extract_at_most(999999999) + if data is None: + return None + return Data(data=data) + + def read_eof(self) -> EndOfMessage: + return EndOfMessage() + + +def expect_nothing(buf: ReceiveBuffer) -> None: + if buf: + raise LocalProtocolError("Got data when expecting EOF") + return None + + +ReadersType = Dict[ + Union[Type[Sentinel], Tuple[Type[Sentinel], Type[Sentinel]]], + Union[Callable[..., Any], Dict[str, Callable[..., Any]]], +] + +READERS: ReadersType = { + (CLIENT, IDLE): maybe_read_from_IDLE_client, + (SERVER, IDLE): maybe_read_from_SEND_RESPONSE_server, + (SERVER, SEND_RESPONSE): maybe_read_from_SEND_RESPONSE_server, + (CLIENT, DONE): expect_nothing, + (CLIENT, MUST_CLOSE): expect_nothing, + (CLIENT, CLOSED): expect_nothing, + (SERVER, DONE): expect_nothing, + (SERVER, MUST_CLOSE): expect_nothing, + (SERVER, CLOSED): expect_nothing, + SEND_BODY: { + "chunked": ChunkedReader, + "content-length": ContentLengthReader, + "http/1.0": Http10Reader, + }, +} diff --git a/.venv/Lib/site-packages/h11/_receivebuffer.py b/.venv/Lib/site-packages/h11/_receivebuffer.py new file mode 100644 index 0000000..e5c4e08 --- /dev/null +++ b/.venv/Lib/site-packages/h11/_receivebuffer.py @@ -0,0 +1,153 @@ +import re +import sys +from typing import List, Optional, Union + +__all__ = ["ReceiveBuffer"] + + +# Operations we want to support: +# - find next \r\n or \r\n\r\n (\n or \n\n are also acceptable), +# or wait until there is one +# - read at-most-N bytes +# Goals: +# - on average, do this fast +# - worst case, do this in O(n) where n is the number of bytes processed +# Plan: +# - store bytearray, offset, how far we've searched for a separator token +# - use the how-far-we've-searched data to avoid rescanning +# - while doing a stream of uninterrupted processing, advance offset instead +# of constantly copying +# WARNING: +# - I haven't benchmarked or profiled any of this yet. +# +# Note that starting in Python 3.4, deleting the initial n bytes from a +# bytearray is amortized O(n), thanks to some excellent work by Antoine +# Martin: +# +# https://bugs.python.org/issue19087 +# +# This means that if we only supported 3.4+, we could get rid of the code here +# involving self._start and self.compress, because it's doing exactly the same +# thing that bytearray now does internally. +# +# BUT unfortunately, we still support 2.7, and reading short segments out of a +# long buffer MUST be O(bytes read) to avoid DoS issues, so we can't actually +# delete this code. Yet: +# +# https://pythonclock.org/ +# +# (Two things to double-check first though: make sure PyPy also has the +# optimization, and benchmark to make sure it's a win, since we do have a +# slightly clever thing where we delay calling compress() until we've +# processed a whole event, which could in theory be slightly more efficient +# than the internal bytearray support.) +blank_line_regex = re.compile(b"\n\r?\n", re.MULTILINE) + + +class ReceiveBuffer: + def __init__(self) -> None: + self._data = bytearray() + self._next_line_search = 0 + self._multiple_lines_search = 0 + + def __iadd__(self, byteslike: Union[bytes, bytearray]) -> "ReceiveBuffer": + self._data += byteslike + return self + + def __bool__(self) -> bool: + return bool(len(self)) + + def __len__(self) -> int: + return len(self._data) + + # for @property unprocessed_data + def __bytes__(self) -> bytes: + return bytes(self._data) + + def _extract(self, count: int) -> bytearray: + # extracting an initial slice of the data buffer and return it + out = self._data[:count] + del self._data[:count] + + self._next_line_search = 0 + self._multiple_lines_search = 0 + + return out + + def maybe_extract_at_most(self, count: int) -> Optional[bytearray]: + """ + Extract a fixed number of bytes from the buffer. + """ + out = self._data[:count] + if not out: + return None + + return self._extract(count) + + def maybe_extract_next_line(self) -> Optional[bytearray]: + """ + Extract the first line, if it is completed in the buffer. + """ + # Only search in buffer space that we've not already looked at. + search_start_index = max(0, self._next_line_search - 1) + partial_idx = self._data.find(b"\r\n", search_start_index) + + if partial_idx == -1: + self._next_line_search = len(self._data) + return None + + # + 2 is to compensate len(b"\r\n") + idx = partial_idx + 2 + + return self._extract(idx) + + def maybe_extract_lines(self) -> Optional[List[bytearray]]: + """ + Extract everything up to the first blank line, and return a list of lines. + """ + # Handle the case where we have an immediate empty line. + if self._data[:1] == b"\n": + self._extract(1) + return [] + + if self._data[:2] == b"\r\n": + self._extract(2) + return [] + + # Only search in buffer space that we've not already looked at. + match = blank_line_regex.search(self._data, self._multiple_lines_search) + if match is None: + self._multiple_lines_search = max(0, len(self._data) - 2) + return None + + # Truncate the buffer and return it. + idx = match.span(0)[-1] + out = self._extract(idx) + lines = out.split(b"\n") + + for line in lines: + if line.endswith(b"\r"): + del line[-1] + + assert lines[-2] == lines[-1] == b"" + + del lines[-2:] + + return lines + + # In theory we should wait until `\r\n` before starting to validate + # incoming data. However it's interesting to detect (very) invalid data + # early given they might not even contain `\r\n` at all (hence only + # timeout will get rid of them). + # This is not a 100% effective detection but more of a cheap sanity check + # allowing for early abort in some useful cases. + # This is especially interesting when peer is messing up with HTTPS and + # sent us a TLS stream where we were expecting plain HTTP given all + # versions of TLS so far start handshake with a 0x16 message type code. + def is_next_line_obviously_invalid_request_line(self) -> bool: + try: + # HTTP header line must not contain non-printable characters + # and should not start with a space + return self._data[0] < 0x21 + except IndexError: + return False diff --git a/.venv/Lib/site-packages/h11/_state.py b/.venv/Lib/site-packages/h11/_state.py new file mode 100644 index 0000000..3ad444b --- /dev/null +++ b/.venv/Lib/site-packages/h11/_state.py @@ -0,0 +1,365 @@ +################################################################ +# The core state machine +################################################################ +# +# Rule 1: everything that affects the state machine and state transitions must +# live here in this file. As much as possible goes into the table-based +# representation, but for the bits that don't quite fit, the actual code and +# state must nonetheless live here. +# +# Rule 2: this file does not know about what role we're playing; it only knows +# about HTTP request/response cycles in the abstract. This ensures that we +# don't cheat and apply different rules to local and remote parties. +# +# +# Theory of operation +# =================== +# +# Possibly the simplest way to think about this is that we actually have 5 +# different state machines here. Yes, 5. These are: +# +# 1) The client state, with its complicated automaton (see the docs) +# 2) The server state, with its complicated automaton (see the docs) +# 3) The keep-alive state, with possible states {True, False} +# 4) The SWITCH_CONNECT state, with possible states {False, True} +# 5) The SWITCH_UPGRADE state, with possible states {False, True} +# +# For (3)-(5), the first state listed is the initial state. +# +# (1)-(3) are stored explicitly in member variables. The last +# two are stored implicitly in the pending_switch_proposals set as: +# (state of 4) == (_SWITCH_CONNECT in pending_switch_proposals) +# (state of 5) == (_SWITCH_UPGRADE in pending_switch_proposals) +# +# And each of these machines has two different kinds of transitions: +# +# a) Event-triggered +# b) State-triggered +# +# Event triggered is the obvious thing that you'd think it is: some event +# happens, and if it's the right event at the right time then a transition +# happens. But there are somewhat complicated rules for which machines can +# "see" which events. (As a rule of thumb, if a machine "sees" an event, this +# means two things: the event can affect the machine, and if the machine is +# not in a state where it expects that event then it's an error.) These rules +# are: +# +# 1) The client machine sees all h11.events objects emitted by the client. +# +# 2) The server machine sees all h11.events objects emitted by the server. +# +# It also sees the client's Request event. +# +# And sometimes, server events are annotated with a _SWITCH_* event. For +# example, we can have a (Response, _SWITCH_CONNECT) event, which is +# different from a regular Response event. +# +# 3) The keep-alive machine sees the process_keep_alive_disabled() event +# (which is derived from Request/Response events), and this event +# transitions it from True -> False, or from False -> False. There's no way +# to transition back. +# +# 4&5) The _SWITCH_* machines transition from False->True when we get a +# Request that proposes the relevant type of switch (via +# process_client_switch_proposals), and they go from True->False when we +# get a Response that has no _SWITCH_* annotation. +# +# So that's event-triggered transitions. +# +# State-triggered transitions are less standard. What they do here is couple +# the machines together. The way this works is, when certain *joint* +# configurations of states are achieved, then we automatically transition to a +# new *joint* state. So, for example, if we're ever in a joint state with +# +# client: DONE +# keep-alive: False +# +# then the client state immediately transitions to: +# +# client: MUST_CLOSE +# +# This is fundamentally different from an event-based transition, because it +# doesn't matter how we arrived at the {client: DONE, keep-alive: False} state +# -- maybe the client transitioned SEND_BODY -> DONE, or keep-alive +# transitioned True -> False. Either way, once this precondition is satisfied, +# this transition is immediately triggered. +# +# What if two conflicting state-based transitions get enabled at the same +# time? In practice there's only one case where this arises (client DONE -> +# MIGHT_SWITCH_PROTOCOL versus DONE -> MUST_CLOSE), and we resolve it by +# explicitly prioritizing the DONE -> MIGHT_SWITCH_PROTOCOL transition. +# +# Implementation +# -------------- +# +# The event-triggered transitions for the server and client machines are all +# stored explicitly in a table. Ditto for the state-triggered transitions that +# involve just the server and client state. +# +# The transitions for the other machines, and the state-triggered transitions +# that involve the other machines, are written out as explicit Python code. +# +# It'd be nice if there were some cleaner way to do all this. This isn't +# *too* terrible, but I feel like it could probably be better. +# +# WARNING +# ------- +# +# The script that generates the state machine diagrams for the docs knows how +# to read out the EVENT_TRIGGERED_TRANSITIONS and STATE_TRIGGERED_TRANSITIONS +# tables. But it can't automatically read the transitions that are written +# directly in Python code. So if you touch those, you need to also update the +# script to keep it in sync! +from typing import cast, Dict, Optional, Set, Tuple, Type, Union + +from ._events import * +from ._util import LocalProtocolError, Sentinel + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = [ + "CLIENT", + "SERVER", + "IDLE", + "SEND_RESPONSE", + "SEND_BODY", + "DONE", + "MUST_CLOSE", + "CLOSED", + "MIGHT_SWITCH_PROTOCOL", + "SWITCHED_PROTOCOL", + "ERROR", +] + + +class CLIENT(Sentinel, metaclass=Sentinel): + pass + + +class SERVER(Sentinel, metaclass=Sentinel): + pass + + +# States +class IDLE(Sentinel, metaclass=Sentinel): + pass + + +class SEND_RESPONSE(Sentinel, metaclass=Sentinel): + pass + + +class SEND_BODY(Sentinel, metaclass=Sentinel): + pass + + +class DONE(Sentinel, metaclass=Sentinel): + pass + + +class MUST_CLOSE(Sentinel, metaclass=Sentinel): + pass + + +class CLOSED(Sentinel, metaclass=Sentinel): + pass + + +class ERROR(Sentinel, metaclass=Sentinel): + pass + + +# Switch types +class MIGHT_SWITCH_PROTOCOL(Sentinel, metaclass=Sentinel): + pass + + +class SWITCHED_PROTOCOL(Sentinel, metaclass=Sentinel): + pass + + +class _SWITCH_UPGRADE(Sentinel, metaclass=Sentinel): + pass + + +class _SWITCH_CONNECT(Sentinel, metaclass=Sentinel): + pass + + +EventTransitionType = Dict[ + Type[Sentinel], + Dict[ + Type[Sentinel], + Dict[Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], Type[Sentinel]], + ], +] + +EVENT_TRIGGERED_TRANSITIONS: EventTransitionType = { + CLIENT: { + IDLE: {Request: SEND_BODY, ConnectionClosed: CLOSED}, + SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, + DONE: {ConnectionClosed: CLOSED}, + MUST_CLOSE: {ConnectionClosed: CLOSED}, + CLOSED: {ConnectionClosed: CLOSED}, + MIGHT_SWITCH_PROTOCOL: {}, + SWITCHED_PROTOCOL: {}, + ERROR: {}, + }, + SERVER: { + IDLE: { + ConnectionClosed: CLOSED, + Response: SEND_BODY, + # Special case: server sees client Request events, in this form + (Request, CLIENT): SEND_RESPONSE, + }, + SEND_RESPONSE: { + InformationalResponse: SEND_RESPONSE, + Response: SEND_BODY, + (InformationalResponse, _SWITCH_UPGRADE): SWITCHED_PROTOCOL, + (Response, _SWITCH_CONNECT): SWITCHED_PROTOCOL, + }, + SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, + DONE: {ConnectionClosed: CLOSED}, + MUST_CLOSE: {ConnectionClosed: CLOSED}, + CLOSED: {ConnectionClosed: CLOSED}, + SWITCHED_PROTOCOL: {}, + ERROR: {}, + }, +} + +StateTransitionType = Dict[ + Tuple[Type[Sentinel], Type[Sentinel]], Dict[Type[Sentinel], Type[Sentinel]] +] + +# NB: there are also some special-case state-triggered transitions hard-coded +# into _fire_state_triggered_transitions below. +STATE_TRIGGERED_TRANSITIONS: StateTransitionType = { + # (Client state, Server state) -> new states + # Protocol negotiation + (MIGHT_SWITCH_PROTOCOL, SWITCHED_PROTOCOL): {CLIENT: SWITCHED_PROTOCOL}, + # Socket shutdown + (CLOSED, DONE): {SERVER: MUST_CLOSE}, + (CLOSED, IDLE): {SERVER: MUST_CLOSE}, + (ERROR, DONE): {SERVER: MUST_CLOSE}, + (DONE, CLOSED): {CLIENT: MUST_CLOSE}, + (IDLE, CLOSED): {CLIENT: MUST_CLOSE}, + (DONE, ERROR): {CLIENT: MUST_CLOSE}, +} + + +class ConnectionState: + def __init__(self) -> None: + # Extra bits of state that don't quite fit into the state model. + + # If this is False then it enables the automatic DONE -> MUST_CLOSE + # transition. Don't set this directly; call .keep_alive_disabled() + self.keep_alive = True + + # This is a subset of {UPGRADE, CONNECT}, containing the proposals + # made by the client for switching protocols. + self.pending_switch_proposals: Set[Type[Sentinel]] = set() + + self.states: Dict[Type[Sentinel], Type[Sentinel]] = {CLIENT: IDLE, SERVER: IDLE} + + def process_error(self, role: Type[Sentinel]) -> None: + self.states[role] = ERROR + self._fire_state_triggered_transitions() + + def process_keep_alive_disabled(self) -> None: + self.keep_alive = False + self._fire_state_triggered_transitions() + + def process_client_switch_proposal(self, switch_event: Type[Sentinel]) -> None: + self.pending_switch_proposals.add(switch_event) + self._fire_state_triggered_transitions() + + def process_event( + self, + role: Type[Sentinel], + event_type: Type[Event], + server_switch_event: Optional[Type[Sentinel]] = None, + ) -> None: + _event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]] = event_type + if server_switch_event is not None: + assert role is SERVER + if server_switch_event not in self.pending_switch_proposals: + raise LocalProtocolError( + "Received server _SWITCH_UPGRADE event without a pending proposal" + ) + _event_type = (event_type, server_switch_event) + if server_switch_event is None and _event_type is Response: + self.pending_switch_proposals = set() + self._fire_event_triggered_transitions(role, _event_type) + # Special case: the server state does get to see Request + # events. + if _event_type is Request: + assert role is CLIENT + self._fire_event_triggered_transitions(SERVER, (Request, CLIENT)) + self._fire_state_triggered_transitions() + + def _fire_event_triggered_transitions( + self, + role: Type[Sentinel], + event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], + ) -> None: + state = self.states[role] + try: + new_state = EVENT_TRIGGERED_TRANSITIONS[role][state][event_type] + except KeyError: + event_type = cast(Type[Event], event_type) + raise LocalProtocolError( + "can't handle event type {} when role={} and state={}".format( + event_type.__name__, role, self.states[role] + ) + ) from None + self.states[role] = new_state + + def _fire_state_triggered_transitions(self) -> None: + # We apply these rules repeatedly until converging on a fixed point + while True: + start_states = dict(self.states) + + # It could happen that both these special-case transitions are + # enabled at the same time: + # + # DONE -> MIGHT_SWITCH_PROTOCOL + # DONE -> MUST_CLOSE + # + # For example, this will always be true of a HTTP/1.0 client + # requesting CONNECT. If this happens, the protocol switch takes + # priority. From there the client will either go to + # SWITCHED_PROTOCOL, in which case it's none of our business when + # they close the connection, or else the server will deny the + # request, in which case the client will go back to DONE and then + # from there to MUST_CLOSE. + if self.pending_switch_proposals: + if self.states[CLIENT] is DONE: + self.states[CLIENT] = MIGHT_SWITCH_PROTOCOL + + if not self.pending_switch_proposals: + if self.states[CLIENT] is MIGHT_SWITCH_PROTOCOL: + self.states[CLIENT] = DONE + + if not self.keep_alive: + for role in (CLIENT, SERVER): + if self.states[role] is DONE: + self.states[role] = MUST_CLOSE + + # Tabular state-triggered transitions + joint_state = (self.states[CLIENT], self.states[SERVER]) + changes = STATE_TRIGGERED_TRANSITIONS.get(joint_state, {}) + self.states.update(changes) + + if self.states == start_states: + # Fixed point reached + return + + def start_next_cycle(self) -> None: + if self.states != {CLIENT: DONE, SERVER: DONE}: + raise LocalProtocolError( + f"not in a reusable state. self.states={self.states}" + ) + # Can't reach DONE/DONE with any of these active, but still, let's be + # sure. + assert self.keep_alive + assert not self.pending_switch_proposals + self.states = {CLIENT: IDLE, SERVER: IDLE} diff --git a/.venv/Lib/site-packages/h11/_util.py b/.venv/Lib/site-packages/h11/_util.py new file mode 100644 index 0000000..6718445 --- /dev/null +++ b/.venv/Lib/site-packages/h11/_util.py @@ -0,0 +1,135 @@ +from typing import Any, Dict, NoReturn, Pattern, Tuple, Type, TypeVar, Union + +__all__ = [ + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", + "validate", + "bytesify", +] + + +class ProtocolError(Exception): + """Exception indicating a violation of the HTTP/1.1 protocol. + + This as an abstract base class, with two concrete base classes: + :exc:`LocalProtocolError`, which indicates that you tried to do something + that HTTP/1.1 says is illegal, and :exc:`RemoteProtocolError`, which + indicates that the remote peer tried to do something that HTTP/1.1 says is + illegal. See :ref:`error-handling` for details. + + In addition to the normal :exc:`Exception` features, it has one attribute: + + .. attribute:: error_status_hint + + This gives a suggestion as to what status code a server might use if + this error occurred as part of a request. + + For a :exc:`RemoteProtocolError`, this is useful as a suggestion for + how you might want to respond to a misbehaving peer, if you're + implementing a server. + + For a :exc:`LocalProtocolError`, this can be taken as a suggestion for + how your peer might have responded to *you* if h11 had allowed you to + continue. + + The default is 400 Bad Request, a generic catch-all for protocol + violations. + + """ + + def __init__(self, msg: str, error_status_hint: int = 400) -> None: + if type(self) is ProtocolError: + raise TypeError("tried to directly instantiate ProtocolError") + Exception.__init__(self, msg) + self.error_status_hint = error_status_hint + + +# Strategy: there are a number of public APIs where a LocalProtocolError can +# be raised (send(), all the different event constructors, ...), and only one +# public API where RemoteProtocolError can be raised +# (receive_data()). Therefore we always raise LocalProtocolError internally, +# and then receive_data will translate this into a RemoteProtocolError. +# +# Internally: +# LocalProtocolError is the generic "ProtocolError". +# Externally: +# LocalProtocolError is for local errors and RemoteProtocolError is for +# remote errors. +class LocalProtocolError(ProtocolError): + def _reraise_as_remote_protocol_error(self) -> NoReturn: + # After catching a LocalProtocolError, use this method to re-raise it + # as a RemoteProtocolError. This method must be called from inside an + # except: block. + # + # An easy way to get an equivalent RemoteProtocolError is just to + # modify 'self' in place. + self.__class__ = RemoteProtocolError # type: ignore + # But the re-raising is somewhat non-trivial -- you might think that + # now that we've modified the in-flight exception object, that just + # doing 'raise' to re-raise it would be enough. But it turns out that + # this doesn't work, because Python tracks the exception type + # (exc_info[0]) separately from the exception object (exc_info[1]), + # and we only modified the latter. So we really do need to re-raise + # the new type explicitly. + # On py3, the traceback is part of the exception object, so our + # in-place modification preserved it and we can just re-raise: + raise self + + +class RemoteProtocolError(ProtocolError): + pass + + +def validate( + regex: Pattern[bytes], data: bytes, msg: str = "malformed data", *format_args: Any +) -> Dict[str, bytes]: + match = regex.fullmatch(data) + if not match: + if format_args: + msg = msg.format(*format_args) + raise LocalProtocolError(msg) + return match.groupdict() + + +# Sentinel values +# +# - Inherit identity-based comparison and hashing from object +# - Have a nice repr +# - Have a *bonus property*: type(sentinel) is sentinel +# +# The bonus property is useful if you want to take the return value from +# next_event() and do some sort of dispatch based on type(event). + +_T_Sentinel = TypeVar("_T_Sentinel", bound="Sentinel") + + +class Sentinel(type): + def __new__( + cls: Type[_T_Sentinel], + name: str, + bases: Tuple[type, ...], + namespace: Dict[str, Any], + **kwds: Any + ) -> _T_Sentinel: + assert bases == (Sentinel,) + v = super().__new__(cls, name, bases, namespace, **kwds) + v.__class__ = v # type: ignore + return v + + def __repr__(self) -> str: + return self.__name__ + + +# Used for methods, request targets, HTTP versions, header names, and header +# values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always +# returns bytes. +def bytesify(s: Union[bytes, bytearray, memoryview, int, str]) -> bytes: + # Fast-path: + if type(s) is bytes: + return s + if isinstance(s, str): + s = s.encode("ascii") + if isinstance(s, int): + raise TypeError("expected bytes-like object, not int") + return bytes(s) diff --git a/.venv/Lib/site-packages/h11/_version.py b/.venv/Lib/site-packages/h11/_version.py new file mode 100644 index 0000000..76e7327 --- /dev/null +++ b/.venv/Lib/site-packages/h11/_version.py @@ -0,0 +1,16 @@ +# This file must be kept very simple, because it is consumed from several +# places -- it is imported by h11/__init__.py, execfile'd by setup.py, etc. + +# We use a simple scheme: +# 1.0.0 -> 1.0.0+dev -> 1.1.0 -> 1.1.0+dev +# where the +dev versions are never released into the wild, they're just what +# we stick into the VCS in between releases. +# +# This is compatible with PEP 440: +# http://legacy.python.org/dev/peps/pep-0440/ +# via the use of the "local suffix" "+dev", which is disallowed on index +# servers and causes 1.0.0+dev to sort after plain 1.0.0, which is what we +# want. (Contrast with the special suffix 1.0.0.dev, which sorts *before* +# 1.0.0.) + +__version__ = "0.16.0" diff --git a/.venv/Lib/site-packages/h11/_writers.py b/.venv/Lib/site-packages/h11/_writers.py new file mode 100644 index 0000000..939cdb9 --- /dev/null +++ b/.venv/Lib/site-packages/h11/_writers.py @@ -0,0 +1,145 @@ +# Code to read HTTP data +# +# Strategy: each writer takes an event + a write-some-bytes function, which is +# calls. +# +# WRITERS is a dict describing how to pick a reader. It maps states to either: +# - a writer +# - or, for body writers, a dict of framin-dependent writer factories + +from typing import Any, Callable, Dict, List, Tuple, Type, Union + +from ._events import Data, EndOfMessage, Event, InformationalResponse, Request, Response +from ._headers import Headers +from ._state import CLIENT, IDLE, SEND_BODY, SEND_RESPONSE, SERVER +from ._util import LocalProtocolError, Sentinel + +__all__ = ["WRITERS"] + +Writer = Callable[[bytes], Any] + + +def write_headers(headers: Headers, write: Writer) -> None: + # "Since the Host field-value is critical information for handling a + # request, a user agent SHOULD generate Host as the first header field + # following the request-line." - RFC 7230 + raw_items = headers._full_items + for raw_name, name, value in raw_items: + if name == b"host": + write(b"%s: %s\r\n" % (raw_name, value)) + for raw_name, name, value in raw_items: + if name != b"host": + write(b"%s: %s\r\n" % (raw_name, value)) + write(b"\r\n") + + +def write_request(request: Request, write: Writer) -> None: + if request.http_version != b"1.1": + raise LocalProtocolError("I only send HTTP/1.1") + write(b"%s %s HTTP/1.1\r\n" % (request.method, request.target)) + write_headers(request.headers, write) + + +# Shared between InformationalResponse and Response +def write_any_response( + response: Union[InformationalResponse, Response], write: Writer +) -> None: + if response.http_version != b"1.1": + raise LocalProtocolError("I only send HTTP/1.1") + status_bytes = str(response.status_code).encode("ascii") + # We don't bother sending ascii status messages like "OK"; they're + # optional and ignored by the protocol. (But the space after the numeric + # status code is mandatory.) + # + # XX FIXME: could at least make an effort to pull out the status message + # from stdlib's http.HTTPStatus table. Or maybe just steal their enums + # (either by import or copy/paste). We already accept them as status codes + # since they're of type IntEnum < int. + write(b"HTTP/1.1 %s %s\r\n" % (status_bytes, response.reason)) + write_headers(response.headers, write) + + +class BodyWriter: + def __call__(self, event: Event, write: Writer) -> None: + if type(event) is Data: + self.send_data(event.data, write) + elif type(event) is EndOfMessage: + self.send_eom(event.headers, write) + else: # pragma: no cover + assert False + + def send_data(self, data: bytes, write: Writer) -> None: + pass + + def send_eom(self, headers: Headers, write: Writer) -> None: + pass + + +# +# These are all careful not to do anything to 'data' except call len(data) and +# write(data). This allows us to transparently pass-through funny objects, +# like placeholder objects referring to files on disk that will be sent via +# sendfile(2). +# +class ContentLengthWriter(BodyWriter): + def __init__(self, length: int) -> None: + self._length = length + + def send_data(self, data: bytes, write: Writer) -> None: + self._length -= len(data) + if self._length < 0: + raise LocalProtocolError("Too much data for declared Content-Length") + write(data) + + def send_eom(self, headers: Headers, write: Writer) -> None: + if self._length != 0: + raise LocalProtocolError("Too little data for declared Content-Length") + if headers: + raise LocalProtocolError("Content-Length and trailers don't mix") + + +class ChunkedWriter(BodyWriter): + def send_data(self, data: bytes, write: Writer) -> None: + # if we encoded 0-length data in the naive way, it would look like an + # end-of-message. + if not data: + return + write(b"%x\r\n" % len(data)) + write(data) + write(b"\r\n") + + def send_eom(self, headers: Headers, write: Writer) -> None: + write(b"0\r\n") + write_headers(headers, write) + + +class Http10Writer(BodyWriter): + def send_data(self, data: bytes, write: Writer) -> None: + write(data) + + def send_eom(self, headers: Headers, write: Writer) -> None: + if headers: + raise LocalProtocolError("can't send trailers to HTTP/1.0 client") + # no need to close the socket ourselves, that will be taken care of by + # Connection: close machinery + + +WritersType = Dict[ + Union[Tuple[Type[Sentinel], Type[Sentinel]], Type[Sentinel]], + Union[ + Dict[str, Type[BodyWriter]], + Callable[[Union[InformationalResponse, Response], Writer], None], + Callable[[Request, Writer], None], + ], +] + +WRITERS: WritersType = { + (CLIENT, IDLE): write_request, + (SERVER, IDLE): write_any_response, + (SERVER, SEND_RESPONSE): write_any_response, + SEND_BODY: { + "chunked": ChunkedWriter, + "content-length": ContentLengthWriter, + "http/1.0": Http10Writer, + }, +} diff --git a/.venv/Lib/site-packages/h11/py.typed b/.venv/Lib/site-packages/h11/py.typed new file mode 100644 index 0000000..f5642f7 --- /dev/null +++ b/.venv/Lib/site-packages/h11/py.typed @@ -0,0 +1 @@ +Marker diff --git a/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/INSTALLER b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/METADATA b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/METADATA new file mode 100644 index 0000000..8056834 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/METADATA @@ -0,0 +1,625 @@ +Metadata-Version: 2.4 +Name: httpcore +Version: 1.0.9 +Summary: A minimal low-level HTTP client. +Project-URL: Documentation, https://www.encode.io/httpcore +Project-URL: Homepage, https://www.encode.io/httpcore/ +Project-URL: Source, https://github.com/encode/httpcore +Author-email: Tom Christie +License-Expression: BSD-3-Clause +License-File: LICENSE.md +Classifier: Development Status :: 3 - Alpha +Classifier: Environment :: Web Environment +Classifier: Framework :: AsyncIO +Classifier: Framework :: Trio +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.8 +Requires-Dist: certifi +Requires-Dist: h11>=0.16 +Provides-Extra: asyncio +Requires-Dist: anyio<5.0,>=4.0; extra == 'asyncio' +Provides-Extra: http2 +Requires-Dist: h2<5,>=3; extra == 'http2' +Provides-Extra: socks +Requires-Dist: socksio==1.*; extra == 'socks' +Provides-Extra: trio +Requires-Dist: trio<1.0,>=0.22.0; extra == 'trio' +Description-Content-Type: text/markdown + +# HTTP Core + +[![Test Suite](https://github.com/encode/httpcore/workflows/Test%20Suite/badge.svg)](https://github.com/encode/httpcore/actions) +[![Package version](https://badge.fury.io/py/httpcore.svg)](https://pypi.org/project/httpcore/) + +> *Do one thing, and do it well.* + +The HTTP Core package provides a minimal low-level HTTP client, which does +one thing only. Sending HTTP requests. + +It does not provide any high level model abstractions over the API, +does not handle redirects, multipart uploads, building authentication headers, +transparent HTTP caching, URL parsing, session cookie handling, +content or charset decoding, handling JSON, environment based configuration +defaults, or any of that Jazz. + +Some things HTTP Core does do: + +* Sending HTTP requests. +* Thread-safe / task-safe connection pooling. +* HTTP(S) proxy & SOCKS proxy support. +* Supports HTTP/1.1 and HTTP/2. +* Provides both sync and async interfaces. +* Async backend support for `asyncio` and `trio`. + +## Requirements + +Python 3.8+ + +## Installation + +For HTTP/1.1 only support, install with: + +```shell +$ pip install httpcore +``` + +There are also a number of optional extras available... + +```shell +$ pip install httpcore['asyncio,trio,http2,socks'] +``` + +## Sending requests + +Send an HTTP request: + +```python +import httpcore + +response = httpcore.request("GET", "https://www.example.com/") + +print(response) +# +print(response.status) +# 200 +print(response.headers) +# [(b'Accept-Ranges', b'bytes'), (b'Age', b'557328'), (b'Cache-Control', b'max-age=604800'), ...] +print(response.content) +# b'\n\n\nExample Domain\n\n\n ...' +``` + +The top-level `httpcore.request()` function is provided for convenience. In practice whenever you're working with `httpcore` you'll want to use the connection pooling functionality that it provides. + +```python +import httpcore + +http = httpcore.ConnectionPool() +response = http.request("GET", "https://www.example.com/") +``` + +Once you're ready to get going, [head over to the documentation](https://www.encode.io/httpcore/). + +## Motivation + +You *probably* don't want to be using HTTP Core directly. It might make sense if +you're writing something like a proxy service in Python, and you just want +something at the lowest possible level, but more typically you'll want to use +a higher level client library, such as `httpx`. + +The motivation for `httpcore` is: + +* To provide a reusable low-level client library, that other packages can then build on top of. +* To provide a *really clear interface split* between the networking code and client logic, + so that each is easier to understand and reason about in isolation. + +## Dependencies + +The `httpcore` package has the following dependencies... + +* `h11` +* `certifi` + +And the following optional extras... + +* `anyio` - Required by `pip install httpcore['asyncio']`. +* `trio` - Required by `pip install httpcore['trio']`. +* `h2` - Required by `pip install httpcore['http2']`. +* `socksio` - Required by `pip install httpcore['socks']`. + +## Versioning + +We use [SEMVER for our versioning policy](https://semver.org/). + +For changes between package versions please see our [project changelog](CHANGELOG.md). + +We recommend pinning your requirements either the most current major version, or a more specific version range: + +```python +pip install 'httpcore==1.*' +``` +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## Version 1.0.9 (April 24th, 2025) + +- Resolve https://github.com/advisories/GHSA-vqfr-h8mv-ghfj with h11 dependency update. (#1008) + +## Version 1.0.8 (April 11th, 2025) + +- Fix `AttributeError` when importing on Python 3.14. (#1005) + +## Version 1.0.7 (November 15th, 2024) + +- Support `proxy=…` configuration on `ConnectionPool()`. (#974) + +## Version 1.0.6 (October 1st, 2024) + +- Relax `trio` dependency pinning. (#956) +- Handle `trio` raising `NotImplementedError` on unsupported platforms. (#955) +- Handle mapping `ssl.SSLError` to `httpcore.ConnectError`. (#918) + +## 1.0.5 (March 27th, 2024) + +- Handle `EndOfStream` exception for anyio backend. (#899) +- Allow trio `0.25.*` series in package dependancies. (#903) + +## 1.0.4 (February 21st, 2024) + +- Add `target` request extension. (#888) +- Fix support for connection `Upgrade` and `CONNECT` when some data in the stream has been read. (#882) + +## 1.0.3 (February 13th, 2024) + +- Fix support for async cancellations. (#880) +- Fix trace extension when used with socks proxy. (#849) +- Fix SSL context for connections using the "wss" scheme (#869) + +## 1.0.2 (November 10th, 2023) + +- Fix `float("inf")` timeouts in `Event.wait` function. (#846) + +## 1.0.1 (November 3rd, 2023) + +- Fix pool timeout to account for the total time spent retrying. (#823) +- Raise a neater RuntimeError when the correct async deps are not installed. (#826) +- Add support for synchronous TLS-in-TLS streams. (#840) + +## 1.0.0 (October 6th, 2023) + +From version 1.0 our async support is now optional, as the package has minimal dependencies by default. + +For async support use either `pip install 'httpcore[asyncio]'` or `pip install 'httpcore[trio]'`. + +The project versioning policy is now explicitly governed by SEMVER. See https://semver.org/. + +- Async support becomes fully optional. (#809) +- Add support for Python 3.12. (#807) + +## 0.18.0 (September 8th, 2023) + +- Add support for HTTPS proxies. (#745, #786) +- Drop Python 3.7 support. (#727) +- Handle `sni_hostname` extension with SOCKS proxy. (#774) +- Handle HTTP/1.1 half-closed connections gracefully. (#641) +- Change the type of `Extensions` from `Mapping[Str, Any]` to `MutableMapping[Str, Any]`. (#762) + +## 0.17.3 (July 5th, 2023) + +- Support async cancellations, ensuring that the connection pool is left in a clean state when cancellations occur. (#726) +- The networking backend interface has [been added to the public API](https://www.encode.io/httpcore/network-backends). Some classes which were previously private implementation detail are now part of the top-level public API. (#699) +- Graceful handling of HTTP/2 GoAway frames, with requests being transparently retried on a new connection. (#730) +- Add exceptions when a synchronous `trace callback` is passed to an asynchronous request or an asynchronous `trace callback` is passed to a synchronous request. (#717) +- Drop Python 3.7 support. (#727) + +## 0.17.2 (May 23th, 2023) + +- Add `socket_options` argument to `ConnectionPool` and `HTTProxy` classes. (#668) +- Improve logging with per-module logger names. (#690) +- Add `sni_hostname` request extension. (#696) +- Resolve race condition during import of `anyio` package. (#692) +- Enable TCP_NODELAY for all synchronous sockets. (#651) + +## 0.17.1 (May 17th, 2023) + +- If 'retries' is set, then allow retries if an SSL handshake error occurs. (#669) +- Improve correctness of tracebacks on network exceptions, by raising properly chained exceptions. (#678) +- Prevent connection-hanging behaviour when HTTP/2 connections are closed by a server-sent 'GoAway' frame. (#679) +- Fix edge-case exception when removing requests from the connection pool. (#680) +- Fix pool timeout edge-case. (#688) + +## 0.17.0 (March 16th, 2023) + +- Add DEBUG level logging. (#648) +- Respect HTTP/2 max concurrent streams when settings updates are sent by server. (#652) +- Increase the allowable HTTP header size to 100kB. (#647) +- Add `retries` option to SOCKS proxy classes. (#643) + +## 0.16.3 (December 20th, 2022) + +- Allow `ws` and `wss` schemes. Allows us to properly support websocket upgrade connections. (#625) +- Forwarding HTTP proxies use a connection-per-remote-host. Required by some proxy implementations. (#637) +- Don't raise `RuntimeError` when closing a connection pool with active connections. Removes some error cases when cancellations are used. (#631) +- Lazy import `anyio`, so that it's no longer a hard dependancy, and isn't imported if unused. (#639) + +## 0.16.2 (November 25th, 2022) + +- Revert 'Fix async cancellation behaviour', which introduced race conditions. (#627) +- Raise `RuntimeError` if attempting to us UNIX domain sockets on Windows. (#619) + +## 0.16.1 (November 17th, 2022) + +- Fix HTTP/1.1 interim informational responses, such as "100 Continue". (#605) + +## 0.16.0 (October 11th, 2022) + +- Support HTTP/1.1 informational responses. (#581) +- Fix async cancellation behaviour. (#580) +- Support `h11` 0.14. (#579) + +## 0.15.0 (May 17th, 2022) + +- Drop Python 3.6 support (#535) +- Ensure HTTP proxy CONNECT requests include `timeout` configuration. (#506) +- Switch to explicit `typing.Optional` for type hints. (#513) +- For `trio` map OSError exceptions to `ConnectError`. (#543) + +## 0.14.7 (February 4th, 2022) + +- Requests which raise a PoolTimeout need to be removed from the pool queue. (#502) +- Fix AttributeError that happened when Socks5Connection were terminated. (#501) + +## 0.14.6 (February 1st, 2022) + +- Fix SOCKS support for `http://` URLs. (#492) +- Resolve race condition around exceptions during streaming a response. (#491) + +## 0.14.5 (January 18th, 2022) + +- SOCKS proxy support. (#478) +- Add proxy_auth argument to HTTPProxy. (#481) +- Improve error message on 'RemoteProtocolError' exception when server disconnects without sending a response. (#479) + +## 0.14.4 (January 5th, 2022) + +- Support HTTP/2 on HTTPS tunnelling proxies. (#468) +- Fix proxy headers missing on HTTP forwarding. (#456) +- Only instantiate SSL context if required. (#457) +- More robust HTTP/2 handling. (#253, #439, #440, #441) + +## 0.14.3 (November 17th, 2021) + +- Fix race condition when removing closed connections from the pool. (#437) + +## 0.14.2 (November 16th, 2021) + +- Failed connections no longer remain in the pool. (Pull #433) + +## 0.14.1 (November 12th, 2021) + +- `max_connections` becomes optional. (Pull #429) +- `certifi` is now included in the install dependancies. (Pull #428) +- `h2` is now strictly optional. (Pull #428) + +## 0.14.0 (November 11th, 2021) + +The 0.14 release is a complete reworking of `httpcore`, comprehensively addressing some underlying issues in the connection pooling, as well as substantially redesigning the API to be more user friendly. + +Some of the lower-level API design also makes the components more easily testable in isolation, and the package now has 100% test coverage. + +See [discussion #419](https://github.com/encode/httpcore/discussions/419) for a little more background. + +There's some other neat bits in there too, such as the "trace" extension, which gives a hook into inspecting the internal events that occur during the request/response cycle. This extension is needed for the HTTPX cli, in order to... + +* Log the point at which the connection is established, and the IP/port on which it is made. +* Determine if the outgoing request should log as HTTP/1.1 or HTTP/2, rather than having to assume it's HTTP/2 if the --http2 flag was passed. (Which may not actually be true.) +* Log SSL version info / certificate info. + +Note that `curio` support is not currently available in 0.14.0. If you're using `httpcore` with `curio` please get in touch, so we can assess if we ought to prioritize it as a feature or not. + +## 0.13.7 (September 13th, 2021) + +- Fix broken error messaging when URL scheme is missing, or a non HTTP(S) scheme is used. (Pull #403) + +## 0.13.6 (June 15th, 2021) + +### Fixed + +- Close sockets when read or write timeouts occur. (Pull #365) + +## 0.13.5 (June 14th, 2021) + +### Fixed + +- Resolved niggles with AnyIO EOF behaviours. (Pull #358, #362) + +## 0.13.4 (June 9th, 2021) + +### Added + +- Improved error messaging when URL scheme is missing, or a non HTTP(S) scheme is used. (Pull #354) + +### Fixed + +- Switched to `anyio` as the default backend implementation when running with `asyncio`. Resolves some awkward [TLS timeout issues](https://github.com/encode/httpx/discussions/1511). + +## 0.13.3 (May 6th, 2021) + +### Added + +- Support HTTP/2 prior knowledge, using `httpcore.SyncConnectionPool(http1=False)`. (Pull #333) + +### Fixed + +- Handle cases where environment does not provide `select.poll` support. (Pull #331) + +## 0.13.2 (April 29th, 2021) + +### Added + +- Improve error message for specific case of `RemoteProtocolError` where server disconnects without sending a response. (Pull #313) + +## 0.13.1 (April 28th, 2021) + +### Fixed + +- More resiliant testing for closed connections. (Pull #311) +- Don't raise exceptions on ungraceful connection closes. (Pull #310) + +## 0.13.0 (April 21st, 2021) + +The 0.13 release updates the core API in order to match the HTTPX Transport API, +introduced in HTTPX 0.18 onwards. + +An example of making requests with the new interface is: + +```python +with httpcore.SyncConnectionPool() as http: + status_code, headers, stream, extensions = http.handle_request( + method=b'GET', + url=(b'https', b'example.org', 443, b'/'), + headers=[(b'host', b'example.org'), (b'user-agent', b'httpcore')] + stream=httpcore.ByteStream(b''), + extensions={} + ) + body = stream.read() + print(status_code, body) +``` + +### Changed + +- The `.request()` method is now `handle_request()`. (Pull #296) +- The `.arequest()` method is now `.handle_async_request()`. (Pull #296) +- The `headers` argument is no longer optional. (Pull #296) +- The `stream` argument is no longer optional. (Pull #296) +- The `ext` argument is now named `extensions`, and is no longer optional. (Pull #296) +- The `"reason"` extension keyword is now named `"reason_phrase"`. (Pull #296) +- The `"reason_phrase"` and `"http_version"` extensions now use byte strings for their values. (Pull #296) +- The `httpcore.PlainByteStream()` class becomes `httpcore.ByteStream()`. (Pull #296) + +### Added + +- Streams now support a `.read()` interface. (Pull #296) + +### Fixed + +- Task cancellation no longer leaks connections from the connection pool. (Pull #305) + +## 0.12.3 (December 7th, 2020) + +### Fixed + +- Abort SSL connections on close rather than waiting for remote EOF when using `asyncio`. (Pull #167) +- Fix exception raised in case of connect timeouts when using the `anyio` backend. (Pull #236) +- Fix `Host` header precedence for `:authority` in HTTP/2. (Pull #241, #243) +- Handle extra edge case when detecting for socket readability when using `asyncio`. (Pull #242, #244) +- Fix `asyncio` SSL warning when using proxy tunneling. (Pull #249) + +## 0.12.2 (November 20th, 2020) + +### Fixed + +- Properly wrap connect errors on the asyncio backend. (Pull #235) +- Fix `ImportError` occurring on Python 3.9 when using the HTTP/1.1 sync client in a multithreaded context. (Pull #237) + +## 0.12.1 (November 7th, 2020) + +### Added + +- Add connect retries. (Pull #221) + +### Fixed + +- Tweak detection of dropped connections, resolving an issue with open files limits on Linux. (Pull #185) +- Avoid leaking connections when establishing an HTTP tunnel to a proxy has failed. (Pull #223) +- Properly wrap OS errors when using `trio`. (Pull #225) + +## 0.12.0 (October 6th, 2020) + +### Changed + +- HTTP header casing is now preserved, rather than always sent in lowercase. (#216 and python-hyper/h11#104) + +### Added + +- Add Python 3.9 to officially supported versions. + +### Fixed + +- Gracefully handle a stdlib asyncio bug when a connection is closed while it is in a paused-for-reading state. (#201) + +## 0.11.1 (September 28nd, 2020) + +### Fixed + +- Add await to async semaphore release() coroutine (#197) +- Drop incorrect curio classifier (#192) + +## 0.11.0 (September 22nd, 2020) + +The Transport API with 0.11.0 has a couple of significant changes. + +Firstly we've moved changed the request interface in order to allow extensions, which will later enable us to support features +such as trailing headers, HTTP/2 server push, and CONNECT/Upgrade connections. + +The interface changes from: + +```python +def request(method, url, headers, stream, timeout): + return (http_version, status_code, reason, headers, stream) +``` + +To instead including an optional dictionary of extensions on the request and response: + +```python +def request(method, url, headers, stream, ext): + return (status_code, headers, stream, ext) +``` + +Having an open-ended extensions point will allow us to add later support for various optional features, that wouldn't otherwise be supported without these API changes. + +In particular: + +* Trailing headers support. +* HTTP/2 Server Push +* sendfile. +* Exposing raw connection on CONNECT, Upgrade, HTTP/2 bi-di streaming. +* Exposing debug information out of the API, including template name, template context. + +Currently extensions are limited to: + +* request: `timeout` - Optional. Timeout dictionary. +* response: `http_version` - Optional. Include the HTTP version used on the response. +* response: `reason` - Optional. Include the reason phrase used on the response. Only valid with HTTP/1.*. + +See https://github.com/encode/httpx/issues/1274#issuecomment-694884553 for the history behind this. + +Secondly, the async version of `request` is now namespaced as `arequest`. + +This allows concrete transports to support both sync and async implementations on the same class. + +### Added + +- Add curio support. (Pull #168) +- Add anyio support, with `backend="anyio"`. (Pull #169) + +### Changed + +- Update the Transport API to use 'ext' for optional extensions. (Pull #190) +- Update the Transport API to use `.request` and `.arequest` so implementations can support both sync and async. (Pull #189) + +## 0.10.2 (August 20th, 2020) + +### Added + +- Added Unix Domain Socket support. (Pull #139) + +### Fixed + +- Always include the port on proxy CONNECT requests. (Pull #154) +- Fix `max_keepalive_connections` configuration. (Pull #153) +- Fixes behaviour in HTTP/1.1 where server disconnects can be used to signal the end of the response body. (Pull #164) + +## 0.10.1 (August 7th, 2020) + +- Include `max_keepalive_connections` on `AsyncHTTPProxy`/`SyncHTTPProxy` classes. + +## 0.10.0 (August 7th, 2020) + +The most notable change in the 0.10.0 release is that HTTP/2 support is now fully optional. + +Use either `pip install httpcore` for HTTP/1.1 support only, or `pip install httpcore[http2]` for HTTP/1.1 and HTTP/2 support. + +### Added + +- HTTP/2 support becomes optional. (Pull #121, #130) +- Add `local_address=...` support. (Pull #100, #134) +- Add `PlainByteStream`, `IteratorByteStream`, `AsyncIteratorByteStream`. The `AsyncByteSteam` and `SyncByteStream` classes are now pure interface classes. (#133) +- Add `LocalProtocolError`, `RemoteProtocolError` exceptions. (Pull #129) +- Add `UnsupportedProtocol` exception. (Pull #128) +- Add `.get_connection_info()` method. (Pull #102, #137) +- Add better TRACE logs. (Pull #101) + +### Changed + +- `max_keepalive` is deprecated in favour of `max_keepalive_connections`. (Pull #140) + +### Fixed + +- Improve handling of server disconnects. (Pull #112) + +## 0.9.1 (May 27th, 2020) + +### Fixed + +- Proper host resolution for sync case, including IPv6 support. (Pull #97) +- Close outstanding connections when connection pool is closed. (Pull #98) + +## 0.9.0 (May 21th, 2020) + +### Changed + +- URL port becomes an `Optional[int]` instead of `int`. (Pull #92) + +### Fixed + +- Honor HTTP/2 max concurrent streams settings. (Pull #89, #90) +- Remove incorrect debug log. (Pull #83) + +## 0.8.4 (May 11th, 2020) + +### Added + +- Logging via HTTPCORE_LOG_LEVEL and HTTPX_LOG_LEVEL environment variables +and TRACE level logging. (Pull #79) + +### Fixed + +- Reuse of connections on HTTP/2 in close concurrency situations. (Pull #81) + +## 0.8.3 (May 6rd, 2020) + +### Fixed + +- Include `Host` and `Accept` headers on proxy "CONNECT" requests. +- De-duplicate any headers also contained in proxy_headers. +- HTTP/2 flag not being passed down to proxy connections. + +## 0.8.2 (May 3rd, 2020) + +### Fixed + +- Fix connections using proxy forwarding requests not being added to the +connection pool properly. (Pull #70) + +## 0.8.1 (April 30th, 2020) + +### Changed + +- Allow inherintance of both `httpcore.AsyncByteStream`, `httpcore.SyncByteStream` without type conflicts. + +## 0.8.0 (April 30th, 2020) + +### Fixed + +- Fixed tunnel proxy support. + +### Added + +- New `TimeoutException` base class. + +## 0.7.0 (March 5th, 2020) + +- First integration with HTTPX. diff --git a/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/RECORD b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/RECORD new file mode 100644 index 0000000..3f37ea0 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/RECORD @@ -0,0 +1,68 @@ +httpcore-1.0.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +httpcore-1.0.9.dist-info/METADATA,sha256=_i1P2mGZEol4d54M8n88BFxTGGP83Zh-rMdPOhjUHCE,21529 +httpcore-1.0.9.dist-info/RECORD,, +httpcore-1.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +httpcore-1.0.9.dist-info/licenses/LICENSE.md,sha256=_ctZFUx0y6uhahEkL3dAvqnyPW_rVUeRfYxflKgDkqU,1518 +httpcore/__init__.py,sha256=9kT_kqChCCJUTHww24ZmR_ezcdbpRYWksD-gYNzkZP8,3445 +httpcore/__pycache__/__init__.cpython-312.pyc,, +httpcore/__pycache__/_api.cpython-312.pyc,, +httpcore/__pycache__/_exceptions.cpython-312.pyc,, +httpcore/__pycache__/_models.cpython-312.pyc,, +httpcore/__pycache__/_ssl.cpython-312.pyc,, +httpcore/__pycache__/_synchronization.cpython-312.pyc,, +httpcore/__pycache__/_trace.cpython-312.pyc,, +httpcore/__pycache__/_utils.cpython-312.pyc,, +httpcore/_api.py,sha256=unZmeDschBWCGCPCwkS3Wot9euK6bg_kKxLtGTxw214,3146 +httpcore/_async/__init__.py,sha256=EWdl2v4thnAHzJpqjU4h2a8DUiGAvNiWrkii9pfhTf0,1221 +httpcore/_async/__pycache__/__init__.cpython-312.pyc,, +httpcore/_async/__pycache__/connection.cpython-312.pyc,, +httpcore/_async/__pycache__/connection_pool.cpython-312.pyc,, +httpcore/_async/__pycache__/http11.cpython-312.pyc,, +httpcore/_async/__pycache__/http2.cpython-312.pyc,, +httpcore/_async/__pycache__/http_proxy.cpython-312.pyc,, +httpcore/_async/__pycache__/interfaces.cpython-312.pyc,, +httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc,, +httpcore/_async/connection.py,sha256=6OcPXqMEfc0BU38_-iHUNDd1vKSTc2UVT09XqNb_BOk,8449 +httpcore/_async/connection_pool.py,sha256=DOIQ2s2ZCf9qfwxhzMprTPLqCL8OxGXiKF6qRHxvVyY,17307 +httpcore/_async/http11.py,sha256=-qM9bV7PjSQF5vxs37-eUXOIFwbIjPcZbNliuX9TtBw,13880 +httpcore/_async/http2.py,sha256=azX1fcmtXaIwjputFlZ4vd92J8xwjGOa9ax9QIv4394,23936 +httpcore/_async/http_proxy.py,sha256=2zVkrlv-Ds-rWGaqaXlrhEJiAQFPo23BT3Gq_sWoBXU,14701 +httpcore/_async/interfaces.py,sha256=jTiaWL83pgpGC9ziv90ZfwaKNMmHwmOalzaKiuTxATo,4455 +httpcore/_async/socks_proxy.py,sha256=lLKgLlggPfhFlqi0ODeBkOWvt9CghBBUyqsnsU1tx6Q,13841 +httpcore/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +httpcore/_backends/__pycache__/__init__.cpython-312.pyc,, +httpcore/_backends/__pycache__/anyio.cpython-312.pyc,, +httpcore/_backends/__pycache__/auto.cpython-312.pyc,, +httpcore/_backends/__pycache__/base.cpython-312.pyc,, +httpcore/_backends/__pycache__/mock.cpython-312.pyc,, +httpcore/_backends/__pycache__/sync.cpython-312.pyc,, +httpcore/_backends/__pycache__/trio.cpython-312.pyc,, +httpcore/_backends/anyio.py,sha256=x8PgEhXRC8bVqsdzk_YJx8Y6d9Tub06CuUSwnbmtqoY,5252 +httpcore/_backends/auto.py,sha256=zO136PKZmsaTDK-HRk84eA-MUg8_2wJf4NvmK432Aio,1662 +httpcore/_backends/base.py,sha256=aShgRdZnMmRhFWHetjumlM73f8Kz1YOAyCUP_4kHslA,3042 +httpcore/_backends/mock.py,sha256=er9T436uSe7NLrfiLa4x6Nuqg5ivQ693CxWYCWsgbH4,4077 +httpcore/_backends/sync.py,sha256=bhE4d9iK9Umxdsdsgm2EfKnXaBms2WggGYU-7jmUujU,7977 +httpcore/_backends/trio.py,sha256=LHu4_Mr5MswQmmT3yE4oLgf9b_JJfeVS4BjDxeJc7Ro,5996 +httpcore/_exceptions.py,sha256=looCKga3_YVYu3s-d3L9RMPRJyhsY7fiuuGxvkOD0c0,1184 +httpcore/_models.py,sha256=IO2CcXcdpovRcLTdGFGB6RyBZdEm2h_TOmoCc4rEKho,17623 +httpcore/_ssl.py,sha256=srqmSNU4iOUvWF-SrJvb8G_YEbHFELOXQOwdDIBTS9c,187 +httpcore/_sync/__init__.py,sha256=JBDIgXt5la1LCJ1sLQeKhjKFpLnpNr8Svs6z2ni3fgg,1141 +httpcore/_sync/__pycache__/__init__.cpython-312.pyc,, +httpcore/_sync/__pycache__/connection.cpython-312.pyc,, +httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc,, +httpcore/_sync/__pycache__/http11.cpython-312.pyc,, +httpcore/_sync/__pycache__/http2.cpython-312.pyc,, +httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc,, +httpcore/_sync/__pycache__/interfaces.cpython-312.pyc,, +httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc,, +httpcore/_sync/connection.py,sha256=9exGOb3PB-Mp2T1-sckSeL2t-tJ_9-NXomV8ihmWCgU,8238 +httpcore/_sync/connection_pool.py,sha256=a-T8LTsUxc7r0Ww1atfHSDoWPjQ0fA8Ul7S3-F0Mj70,16955 +httpcore/_sync/http11.py,sha256=IFobD1Md5JFlJGKWnh1_Q3epikUryI8qo09v8MiJIEA,13476 +httpcore/_sync/http2.py,sha256=AxU4yhcq68Bn5vqdJYtiXKYUj7nvhYbxz3v4rT4xnvA,23400 +httpcore/_sync/http_proxy.py,sha256=_al_6crKuEZu2wyvu493RZImJdBJnj5oGKNjLOJL2Zo,14463 +httpcore/_sync/interfaces.py,sha256=snXON42vUDHO5JBJvo8D4VWk2Wat44z2OXXHDrjbl94,4344 +httpcore/_sync/socks_proxy.py,sha256=zegZW9Snqj2_992DFJa8_CppOVBkVL4AgwduRkStakQ,13614 +httpcore/_synchronization.py,sha256=zSi13mAColBnknjZBknUC6hKNDQT4C6ijnezZ-r0T2s,9434 +httpcore/_trace.py,sha256=ck6ZoIzYTkdNAIfq5MGeKqBXDtqjOX-qfYwmZFbrGco,3952 +httpcore/_utils.py,sha256=_RLgXYOAYC350ikALV59GZ68IJrdocRZxPs9PjmzdFY,1537 +httpcore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/WHEEL b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/WHEEL new file mode 100644 index 0000000..12228d4 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000..311b2b5 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md @@ -0,0 +1,27 @@ +Copyright © 2020, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.venv/Lib/site-packages/httpcore/__init__.py b/.venv/Lib/site-packages/httpcore/__init__.py new file mode 100644 index 0000000..9a92dc4 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/__init__.py @@ -0,0 +1,141 @@ +from ._api import request, stream +from ._async import ( + AsyncConnectionInterface, + AsyncConnectionPool, + AsyncHTTP2Connection, + AsyncHTTP11Connection, + AsyncHTTPConnection, + AsyncHTTPProxy, + AsyncSOCKSProxy, +) +from ._backends.base import ( + SOCKET_OPTION, + AsyncNetworkBackend, + AsyncNetworkStream, + NetworkBackend, + NetworkStream, +) +from ._backends.mock import AsyncMockBackend, AsyncMockStream, MockBackend, MockStream +from ._backends.sync import SyncBackend +from ._exceptions import ( + ConnectError, + ConnectionNotAvailable, + ConnectTimeout, + LocalProtocolError, + NetworkError, + PoolTimeout, + ProtocolError, + ProxyError, + ReadError, + ReadTimeout, + RemoteProtocolError, + TimeoutException, + UnsupportedProtocol, + WriteError, + WriteTimeout, +) +from ._models import URL, Origin, Proxy, Request, Response +from ._ssl import default_ssl_context +from ._sync import ( + ConnectionInterface, + ConnectionPool, + HTTP2Connection, + HTTP11Connection, + HTTPConnection, + HTTPProxy, + SOCKSProxy, +) + +# The 'httpcore.AnyIOBackend' class is conditional on 'anyio' being installed. +try: + from ._backends.anyio import AnyIOBackend +except ImportError: # pragma: nocover + + class AnyIOBackend: # type: ignore + def __init__(self, *args, **kwargs): # type: ignore + msg = ( + "Attempted to use 'httpcore.AnyIOBackend' but 'anyio' is not installed." + ) + raise RuntimeError(msg) + + +# The 'httpcore.TrioBackend' class is conditional on 'trio' being installed. +try: + from ._backends.trio import TrioBackend +except ImportError: # pragma: nocover + + class TrioBackend: # type: ignore + def __init__(self, *args, **kwargs): # type: ignore + msg = "Attempted to use 'httpcore.TrioBackend' but 'trio' is not installed." + raise RuntimeError(msg) + + +__all__ = [ + # top-level requests + "request", + "stream", + # models + "Origin", + "URL", + "Request", + "Response", + "Proxy", + # async + "AsyncHTTPConnection", + "AsyncConnectionPool", + "AsyncHTTPProxy", + "AsyncHTTP11Connection", + "AsyncHTTP2Connection", + "AsyncConnectionInterface", + "AsyncSOCKSProxy", + # sync + "HTTPConnection", + "ConnectionPool", + "HTTPProxy", + "HTTP11Connection", + "HTTP2Connection", + "ConnectionInterface", + "SOCKSProxy", + # network backends, implementations + "SyncBackend", + "AnyIOBackend", + "TrioBackend", + # network backends, mock implementations + "AsyncMockBackend", + "AsyncMockStream", + "MockBackend", + "MockStream", + # network backends, interface + "AsyncNetworkStream", + "AsyncNetworkBackend", + "NetworkStream", + "NetworkBackend", + # util + "default_ssl_context", + "SOCKET_OPTION", + # exceptions + "ConnectionNotAvailable", + "ProxyError", + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", + "UnsupportedProtocol", + "TimeoutException", + "PoolTimeout", + "ConnectTimeout", + "ReadTimeout", + "WriteTimeout", + "NetworkError", + "ConnectError", + "ReadError", + "WriteError", +] + +__version__ = "1.0.9" + + +__locals = locals() +for __name in __all__: + # Exclude SOCKET_OPTION, it causes AttributeError on Python 3.14 + if not __name.startswith(("__", "SOCKET_OPTION")): + setattr(__locals[__name], "__module__", "httpcore") # noqa diff --git a/.venv/Lib/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..621df14381ade0f930220873d556a7c94d4e2683 GIT binary patch literal 3182 zcmbVO&2JmW6`x&xir*$B*_LH{Wyw}(NwnqoBerEHEM+%BTuG4RCK<3;thi&D)()4M zSxT1B0z%;e1?od?$+76o@TGr4eC#DbV6>gqO@Lg|8y&S!kzP7)b}3Rq&_fpBZ|BW> zGkov&-kaG!rPB!{pJ%lj4_j%3z5${0M1#TZKQx3sK{m2A2T=`cRL8movhIXv7>8*D zM`#pBX$;3`9LH%N?xP8uP&OeaNmDpQ(>P5tI772IOLI7d3Coj*YF5?VAY5y9MUYht|R)@>SxpopA`Q}=6?0*6Wo}_UaAcl%n=V`b ziB(%C4O`^ZvS4yqMI$nYY|;nR#jwb!ML%`pi0+uD z4_YP^MZR6%60z)H2@E?+3QL5#9yz!$7ijN1u8}6>SrqO!c&piTnMdr7tw_Aj>K+Na zQsZEq%u49~(mfGfVD+_nLqwD#BDNG1RD6l>rrY2|@>Hb7et2_GG0M zx(s!TtV56VNYz*NaB1aW;y_hp4i!`%$i6#?o-)!zs@&L`TL=VFW;Xa zg0N;ItlQcXghN|;F~lxOMJ?!CWea_pmA~C#3G%$ueiice^}h^tdi>W~QJ>$wTlPFc zo03_>bBz`!#`r_eYt~#wO1(~v8>=nP7`GZ*b$8sTbEDyUM!ms3%W;TZdM0C7YBfAr zIw~d4!XnIx^FV|xw#G$teN(+dl&|rybjy&>_fF+T^*$$zSC=hXoh5wTbDP!Wx=jp8 z(j(dByn5H-UU_lO*x<%p$KvbNccH78G3!2ZtECOn*r?vCuU5INg{h`gS!;w>I}uzk z&3dEmnP#cECE^`nd!RS4QfK;_bc-LAu!FwNeb5e}bnahKR5<-9|1v-Nd1`c5s?fX) zOm5V$C^1cuFiq;(EeFOa)BLDqIlhM7lD5wfyDeXA1^}|8;Hn((n1m;2R}c4%`8J@2 zzFlE;_s5mNtlZ(pm%;4+DT5xLf-3N&mp@Ph?27b(DIPmH_JapDw)=bb*(V*m=1|%@ z=r8#Xep~r_{#1u=Ma(>V8}_UKbjgHO3;^c$$<#Y!f~GjlpbuCW;2gkNfHwe&0OJ6C z0Puam;PZpU0U$Rl0+0l_05Az~5ugMx1uy|H4KM(p6096M0wTo33IP28836fVgIEp# z+LN6JNC80Av6BGMvJ7rqW&oT47y&o|a17uyz!<;~z#zaWfKdtCk?GRK(sg-(>Za*$ zUY64$-f4;X;;aapR{2d-QWM@%q^`iG53$di_Ne6#L76fsUb z%k$Wwwh-|d|1`SIs?s$$^7*$C+96HTzCt5=dR)`@kc8I&d-}s&w7%Q6rSCU*MEugN1HmS#Nf74t)`9&RD!fcvYM0xs?=&+p*pVYjdv0|S?`*e zbsZxkA=FFxQE!w3M-D`R100bM>X{3FQlmwcka~dLTvDkh;=r5PT{}sOKyX6pZZh-c z&6_uG-oE+X^EZ8c5d_bAX|nuEFG6?uz<&ZRA#HsD#7(3lT{2Kr%1IKZWkb%%qE~W? zuIN2RPc@JWRMnhX4d#N?P%Z>=iXJe+)krP^v@BlrBPEO;_v&FicumbkiM(>aH=*1A z?dpggx~6V;3vzu;IT^jjM@h+vYNlz~nq9F>Hg`|r%pOO%bn(0snO?Jr$@qj5ogtb| zXtr4+%n4m2tOi^H%VTFP((s26%h)ax7`aDIMZ}t7 zu_$_XJ{Svu9^G54I{Mrl!wNXQvazR7YQ(8m?UsDttrk zq@4~-;pqzSDAt5;D<-owv*g;=aTToHVbuwfuZV zCpbf`6~G~uKc_J}otec~89rxdY&Acll~y4Rr>qq#pSnuStNHVl#XJMIAFsibwoF*w zkI0FFR;#3HO*+WU;SD#5y#UQy=)q|eIq+jR{$X~rfA~i0dTVnyzBw?w`P3Ny8+oQ3 zMFR)7kU9`~sG!K`?Qs12aD1C_n0t5sF=Zq0eDD$cJH+wt6Oecn(CNQpARwmNQa?~` zN`6l1qM@55y5pw27aiXla?x=w-$%{Q6oH&p&%Z%V+yFeNm{`O9#i?p0I3MC(1e_14 zT&xSGfCNOV{sS!Ku;HU|7eG@f_f~P`Vwcy-#Kd*x!+O!vpDf~~x+x%IT-+Ugi@P`5 z#eO(}VPMFXc##NYTHUtz-E)x_4AoP3b_qAFI&NsDjcp5Sr4nJxyN;e3p_>b_vQ;;9 zp^CU*S!VSRIZz=a3Q-Q97YP>QV{jL`Xs3UZahgU;DrBm zQlT+`$WHE1-CLUs{$=W*Lm=;jd_Z797=jP@Lx+K;`~2ovAlL#lzoOp(;%B4|#`g{0 zSh&9MD8|R)50Dy*w0lu(|K`x>J97{FVJIN~+i?7$%;&hkw$J*5r9l7N(e#k?{zzI? zJ`PL<)zZ<$239gN|_JB1Q9` zA8wxRY)Ipr$Fn;f<;@s0Z^ZX*?w1Kyk})SzC@j^Xye5SL<<-3t5x})(8iqDX#*k*pl1j>?^RQS%Ny+|MgMewSp)uMA~*@o<=Xph!4JwwqPw15#WSQ zgZjEbUZRJA$A5dUub^owk|fyT8(Y>b1EYtx%aj-`kgcil3kt0 nq%olrdZ80~Pba5bot$!Yatd@l{aZg!TMClL?#n~ ztyOGJ!WJ$Eur>m?I@a|G*(JiaqC)l(M1hR>LuuMBXJS#|B9`nX)TaaLg@e z8&H>pdIWU?^;oD^P%luQh57{b0}U`=J+EKTcA!BE4G4M!Xon?3yPzSUovgi{At-1U z&~6JoBIr?|Jr>#_XfMz{3k?a{4|Kqy)+y*QpvPHfU9C&dmw<*Xv|G>Y~&G;MA6Hl6=%hNL(UhOEN`T>oasm#EUTNIw4ToChMG&VsKRkbrp#87rfZIC z*#vq{L&LX@dp!*0AMN~j{JPG#9-mdS@kypH7+O9)n@+Lt4AE$MOX; z0JCmE+z%sZ!x^{&=1=ncQ&Lu(j=)ow|M^pI=QE->0*}Lk_uY5h_q-+Fz@3Ymb5GlQ zi-X0LU)qO%>mDf1ezmZz?*&hl9puF6vXi(2|CZg*>xCYLjO%p5MK=^Txr5GU?g@wd zWn_ZL-+RG4A`YlOa>x)$LgRX=?S_3ky<3El7)_Cw1e-@PyU%O69Lx*M`?O|EEUD>? zdNaeOcVyF*NaWNkOC(HBB9YZniy8ENiNwc?YNis|A^WJpF)?qinGSIFabs+d$BK)0 z)wu0Jd0??t3R9eGe3}QbdnX!^NJ#Zy6-_rf!iXRu+bW^!Iejso*SNt_AZTbwEpxaW zY0t;W8&NJDS&;Wf0kx z2~>|@OC>R9Xx?mALS$RXdzGoFmNLVb9nG0$8AP^ad>?Z7dA8I!h3V0}-K>MizRol= zmNmZ6;y|6o^k^oVbr9LtsV-0poiR+0=3=uBBHKD{T%HypuVQXAmzzZp*%tAL#c84P zIwnUm)2xCh+GBc>TFziAMkaj|?%zT_ox5eWT~YJGQLYv8j60#F8LXesS};)UnN@Re zr*hME$zWVHG%jw<%Jm_B?BXvgeud%(6~=Lh11*l5I0<5(icKU|SWLf|9x?jn+ro66 zt=wMc_&B7(+feTZTb3!MPsouc#Q%G!ckA32=eCFLkK7%3kS;~udN}ms$PXhwrAy;8 zdy$z^=>0tsgzH%H{Yv{vw683+(ZF8MaGAjS!3W{{apkQYQca literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/__pycache__/_models.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/__pycache__/_models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17f79a4b4382bcf9f276da2fd0aa66de3f4c3d0b GIT binary patch literal 23148 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5889eece5d744891cfc1ca5219c3ab17b78bb1a GIT binary patch literal 14169 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 E2b!PbzyJUM literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4c52890793b215cbf325a5cc96a93d04d98f3a2 GIT binary patch literal 1306 zcmYjQ&1)M+6rWkGRN*{>}%p#i`n^^+WJ#K4ocB(n=Jz|aLAtU#!E&DAPeOjDIqO!bP6b)5QEucW(L zDg9Wvmtd5$eV+7fdbId$3{~G>+MAV_=_Tw2t_vGa2ms*M4(Q8QOt}u8Rxl0A^{+T4cGt; zS@>|A#gv{st(WShL@u!lwc!~12AGPMPxKS|ydFK&Tg8by;X*jU*?J+7PUtWh517iX zw+a)Bgp-KJm=d}HO2*B9XOTQ#5Yb^i+G-NfBn;Vz(^{K|OUUNP!NeC%onTb$B1dpk zcU*!n;Wg$2g0f4<;fN6%+f|p8<8TYh;{emD1>_Oi=SVbdAukg9plYXHml+-IUl53R zf#}M3Wl|oQ)oj;w{6-wK%t&o>v4p5EQJ^i_aWQf{k6_0Z#O=y(aSfb$5}hsV5tKEi zI`n0f-g5kR--+~qx^8JEGE@;hjZ(bJqio>XqE49?Wg0}jQNbdE6PMIPq*p0*nR=p8 zmXU_T1(C}2&sSHkt(DS|uKE;Z)NR#qWH8l-ZUn6e20SgjRCCe)txuQCuQ*}c+^{|K z3gK-*19L-m7OgYdl8LyvVso*)ehqDMwBp*lZCK>zIp{{G9d3=LXC*^nFG(coG4mhf)dx>r0dw;McN_nv|B>6z z4GaCxjxwkE-`u;fxBSz^qvDyTr@>7APJTOoFY{;iwO`-={qn)(`|mwG|MB7ZtN+cN z+wI-$?KSpp9B2oh+=mb6mbUY^jR73IeOx>yzZ8Bf{H4EsoGT9Cu5s5GeEt2*&PRv(t0xi{DCtaXz|FHG_~j8nAzGt8k-d%Q z%hVE;jA+)f>K!?7Vp&Y>E>p`V#}>a^rizWL-UsYu#h+E$><1pj9hZE_=H;*22CkG( XQV_y_z``R?_*;FmPmObMKK6SCR5CrS literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_api.py b/.venv/Lib/site-packages/httpcore/_api.py new file mode 100644 index 0000000..38b961d --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_api.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +import contextlib +import typing + +from ._models import URL, Extensions, HeaderTypes, Response +from ._sync.connection_pool import ConnectionPool + + +def request( + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, +) -> Response: + """ + Sends an HTTP request, returning the response. + + ``` + response = httpcore.request("GET", "https://www.example.com/") + ``` + + Arguments: + method: The HTTP method for the request. Typically one of `"GET"`, + `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`. + url: The URL of the HTTP request. Either as an instance of `httpcore.URL`, + or as str/bytes. + headers: The HTTP request headers. Either as a dictionary of str/bytes, + or as a list of two-tuples of str/bytes. + content: The content of the request body. Either as bytes, + or as a bytes iterator. + extensions: A dictionary of optional extra information included on the request. + Possible keys include `"timeout"`. + + Returns: + An instance of `httpcore.Response`. + """ + with ConnectionPool() as pool: + return pool.request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + + +@contextlib.contextmanager +def stream( + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.Iterator[bytes] | None = None, + extensions: Extensions | None = None, +) -> typing.Iterator[Response]: + """ + Sends an HTTP request, returning the response within a content manager. + + ``` + with httpcore.stream("GET", "https://www.example.com/") as response: + ... + ``` + + When using the `stream()` function, the body of the response will not be + automatically read. If you want to access the response body you should + either use `content = response.read()`, or `for chunk in response.iter_content()`. + + Arguments: + method: The HTTP method for the request. Typically one of `"GET"`, + `"OPTIONS"`, `"HEAD"`, `"POST"`, `"PUT"`, `"PATCH"`, or `"DELETE"`. + url: The URL of the HTTP request. Either as an instance of `httpcore.URL`, + or as str/bytes. + headers: The HTTP request headers. Either as a dictionary of str/bytes, + or as a list of two-tuples of str/bytes. + content: The content of the request body. Either as bytes, + or as a bytes iterator. + extensions: A dictionary of optional extra information included on the request. + Possible keys include `"timeout"`. + + Returns: + An instance of `httpcore.Response`. + """ + with ConnectionPool() as pool: + with pool.stream( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) as response: + yield response diff --git a/.venv/Lib/site-packages/httpcore/_async/__init__.py b/.venv/Lib/site-packages/httpcore/_async/__init__.py new file mode 100644 index 0000000..88dc7f0 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_async/__init__.py @@ -0,0 +1,39 @@ +from .connection import AsyncHTTPConnection +from .connection_pool import AsyncConnectionPool +from .http11 import AsyncHTTP11Connection +from .http_proxy import AsyncHTTPProxy +from .interfaces import AsyncConnectionInterface + +try: + from .http2 import AsyncHTTP2Connection +except ImportError: # pragma: nocover + + class AsyncHTTP2Connection: # type: ignore + def __init__(self, *args, **kwargs) -> None: # type: ignore + raise RuntimeError( + "Attempted to use http2 support, but the `h2` package is not " + "installed. Use 'pip install httpcore[http2]'." + ) + + +try: + from .socks_proxy import AsyncSOCKSProxy +except ImportError: # pragma: nocover + + class AsyncSOCKSProxy: # type: ignore + def __init__(self, *args, **kwargs) -> None: # type: ignore + raise RuntimeError( + "Attempted to use SOCKS support, but the `socksio` package is not " + "installed. Use 'pip install httpcore[socks]'." + ) + + +__all__ = [ + "AsyncHTTPConnection", + "AsyncConnectionPool", + "AsyncHTTPProxy", + "AsyncHTTP11Connection", + "AsyncHTTP2Connection", + "AsyncConnectionInterface", + "AsyncSOCKSProxy", +] diff --git a/.venv/Lib/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ae5360020eabe58f609fa88b34bcd2e76cb480b GIT binary patch literal 1639 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 G#{UNaj534( literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f895e03c119bd060888b91b498af327b93d49913 GIT binary patch literal 19614 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 D1EWdL literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f187f5f061c60451fcbd9fc3865cceb94a81b70 GIT binary patch literal 20125 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%0A>=>My|nmio52hxm5&hK@UB)bPZKmldNbLE7})_ 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 K;K*g;|33jrUmWQG literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e79f85aa66f08fbe02c429aa089de886acce2a3 GIT binary patch literal 17986 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 X-VF-Ujf1*z9lSDoeoi4mChPwPM?5uJ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ca9fa51f5304f8278a25f1bef1c8793f8298ff5 GIT binary patch literal 5770 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 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_async/connection.py b/.venv/Lib/site-packages/httpcore/_async/connection.py new file mode 100644 index 0000000..b42581d --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_async/connection.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import itertools +import logging +import ssl +import types +import typing + +from .._backends.auto import AutoBackend +from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream +from .._exceptions import ConnectError, ConnectTimeout +from .._models import Origin, Request, Response +from .._ssl import default_ssl_context +from .._synchronization import AsyncLock +from .._trace import Trace +from .http11 import AsyncHTTP11Connection +from .interfaces import AsyncConnectionInterface + +RETRIES_BACKOFF_FACTOR = 0.5 # 0s, 0.5s, 1s, 2s, 4s, etc. + + +logger = logging.getLogger("httpcore.connection") + + +def exponential_backoff(factor: float) -> typing.Iterator[float]: + """ + Generate a geometric sequence that has a ratio of 2 and starts with 0. + + For example: + - `factor = 2`: `0, 2, 4, 8, 16, 32, 64, ...` + - `factor = 3`: `0, 3, 6, 12, 24, 48, 96, ...` + """ + yield 0 + for n in itertools.count(): + yield factor * 2**n + + +class AsyncHTTPConnection(AsyncConnectionInterface): + def __init__( + self, + origin: Origin, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + self._origin = origin + self._ssl_context = ssl_context + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._retries = retries + self._local_address = local_address + self._uds = uds + + self._network_backend: AsyncNetworkBackend = ( + AutoBackend() if network_backend is None else network_backend + ) + self._connection: AsyncConnectionInterface | None = None + self._connect_failed: bool = False + self._request_lock = AsyncLock() + self._socket_options = socket_options + + async def handle_async_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection to {self._origin}" + ) + + try: + async with self._request_lock: + if self._connection is None: + stream = await self._connect(request) + + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + if http2_negotiated or (self._http2 and not self._http1): + from .http2 import AsyncHTTP2Connection + + self._connection = AsyncHTTP2Connection( + origin=self._origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = AsyncHTTP11Connection( + origin=self._origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + except BaseException as exc: + self._connect_failed = True + raise exc + + return await self._connection.handle_async_request(request) + + async def _connect(self, request: Request) -> AsyncNetworkStream: + timeouts = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname", None) + timeout = timeouts.get("connect", None) + + retries_left = self._retries + delays = exponential_backoff(factor=RETRIES_BACKOFF_FACTOR) + + while True: + try: + if self._uds is None: + kwargs = { + "host": self._origin.host.decode("ascii"), + "port": self._origin.port, + "local_address": self._local_address, + "timeout": timeout, + "socket_options": self._socket_options, + } + async with Trace("connect_tcp", logger, request, kwargs) as trace: + stream = await self._network_backend.connect_tcp(**kwargs) + trace.return_value = stream + else: + kwargs = { + "path": self._uds, + "timeout": timeout, + "socket_options": self._socket_options, + } + async with Trace( + "connect_unix_socket", logger, request, kwargs + ) as trace: + stream = await self._network_backend.connect_unix_socket( + **kwargs + ) + trace.return_value = stream + + if self._origin.scheme in (b"https", b"wss"): + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": sni_hostname + or self._origin.host.decode("ascii"), + "timeout": timeout, + } + async with Trace("start_tls", logger, request, kwargs) as trace: + stream = await stream.start_tls(**kwargs) + trace.return_value = stream + return stream + except (ConnectError, ConnectTimeout): + if retries_left <= 0: + raise + retries_left -= 1 + delay = next(delays) + async with Trace("retry", logger, request, kwargs) as trace: + await self._network_backend.sleep(delay) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + async def aclose(self) -> None: + if self._connection is not None: + async with Trace("close", logger, None, {}): + await self._connection.aclose() + + def is_available(self) -> bool: + if self._connection is None: + # If HTTP/2 support is enabled, and the resulting connection could + # end up as HTTP/2 then we should indicate the connection as being + # available to service multiple requests. + return ( + self._http2 + and (self._origin.scheme == b"https" or not self._http1) + and not self._connect_failed + ) + return self._connection.is_available() + + def has_expired(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.has_expired() + + def is_idle(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.is_idle() + + def is_closed(self) -> bool: + if self._connection is None: + return self._connect_failed + return self._connection.is_closed() + + def info(self) -> str: + if self._connection is None: + return "CONNECTION FAILED" if self._connect_failed else "CONNECTING" + return self._connection.info() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + async def __aenter__(self) -> AsyncHTTPConnection: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + await self.aclose() diff --git a/.venv/Lib/site-packages/httpcore/_async/connection_pool.py b/.venv/Lib/site-packages/httpcore/_async/connection_pool.py new file mode 100644 index 0000000..96e973d --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_async/connection_pool.py @@ -0,0 +1,420 @@ +from __future__ import annotations + +import ssl +import sys +import types +import typing + +from .._backends.auto import AutoBackend +from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend +from .._exceptions import ConnectionNotAvailable, UnsupportedProtocol +from .._models import Origin, Proxy, Request, Response +from .._synchronization import AsyncEvent, AsyncShieldCancellation, AsyncThreadLock +from .connection import AsyncHTTPConnection +from .interfaces import AsyncConnectionInterface, AsyncRequestInterface + + +class AsyncPoolRequest: + def __init__(self, request: Request) -> None: + self.request = request + self.connection: AsyncConnectionInterface | None = None + self._connection_acquired = AsyncEvent() + + def assign_to_connection(self, connection: AsyncConnectionInterface | None) -> None: + self.connection = connection + self._connection_acquired.set() + + def clear_connection(self) -> None: + self.connection = None + self._connection_acquired = AsyncEvent() + + async def wait_for_connection( + self, timeout: float | None = None + ) -> AsyncConnectionInterface: + if self.connection is None: + await self._connection_acquired.wait(timeout=timeout) + assert self.connection is not None + return self.connection + + def is_queued(self) -> bool: + return self.connection is None + + +class AsyncConnectionPool(AsyncRequestInterface): + """ + A connection pool for making HTTP requests. + """ + + def __init__( + self, + ssl_context: ssl.SSLContext | None = None, + proxy: Proxy | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish a + connection. + local_address: Local address to connect from. Can also be used to connect + using a particular address family. Using `local_address="0.0.0.0"` + will connect using an `AF_INET` address (IPv4), while using + `local_address="::"` will connect using an `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + socket_options: Socket options that have to be included + in the TCP socket when the connection was established. + """ + self._ssl_context = ssl_context + self._proxy = proxy + self._max_connections = ( + sys.maxsize if max_connections is None else max_connections + ) + self._max_keepalive_connections = ( + sys.maxsize + if max_keepalive_connections is None + else max_keepalive_connections + ) + self._max_keepalive_connections = min( + self._max_connections, self._max_keepalive_connections + ) + + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._retries = retries + self._local_address = local_address + self._uds = uds + + self._network_backend = ( + AutoBackend() if network_backend is None else network_backend + ) + self._socket_options = socket_options + + # The mutable state on a connection pool is the queue of incoming requests, + # and the set of connections that are servicing those requests. + self._connections: list[AsyncConnectionInterface] = [] + self._requests: list[AsyncPoolRequest] = [] + + # We only mutate the state of the connection pool within an 'optional_thread_lock' + # context. This holds a threading lock unless we're running in async mode, + # in which case it is a no-op. + self._optional_thread_lock = AsyncThreadLock() + + def create_connection(self, origin: Origin) -> AsyncConnectionInterface: + if self._proxy is not None: + if self._proxy.url.scheme in (b"socks5", b"socks5h"): + from .socks_proxy import AsyncSocks5Connection + + return AsyncSocks5Connection( + proxy_origin=self._proxy.url.origin, + proxy_auth=self._proxy.auth, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + elif origin.scheme == b"http": + from .http_proxy import AsyncForwardHTTPConnection + + return AsyncForwardHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + ) + from .http_proxy import AsyncTunnelHTTPConnection + + return AsyncTunnelHTTPConnection( + proxy_origin=self._proxy.url.origin, + proxy_headers=self._proxy.headers, + proxy_ssl_context=self._proxy.ssl_context, + remote_origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + return AsyncHTTPConnection( + origin=origin, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + retries=self._retries, + local_address=self._local_address, + uds=self._uds, + network_backend=self._network_backend, + socket_options=self._socket_options, + ) + + @property + def connections(self) -> list[AsyncConnectionInterface]: + """ + Return a list of the connections currently in the pool. + + For example: + + ```python + >>> pool.connections + [ + , + , + , + ] + ``` + """ + return list(self._connections) + + async def handle_async_request(self, request: Request) -> Response: + """ + Send an HTTP request, and return an HTTP response. + + This is the core implementation that is called into by `.request()` or `.stream()`. + """ + scheme = request.url.scheme.decode() + if scheme == "": + raise UnsupportedProtocol( + "Request URL is missing an 'http://' or 'https://' protocol." + ) + if scheme not in ("http", "https", "ws", "wss"): + raise UnsupportedProtocol( + f"Request URL has an unsupported protocol '{scheme}://'." + ) + + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("pool", None) + + with self._optional_thread_lock: + # Add the incoming request to our request queue. + pool_request = AsyncPoolRequest(request) + self._requests.append(pool_request) + + try: + while True: + with self._optional_thread_lock: + # Assign incoming requests to available connections, + # closing or creating new connections as required. + closing = self._assign_requests_to_connections() + await self._close_connections(closing) + + # Wait until this request has an assigned connection. + connection = await pool_request.wait_for_connection(timeout=timeout) + + try: + # Send the request on the assigned connection. + response = await connection.handle_async_request( + pool_request.request + ) + except ConnectionNotAvailable: + # In some cases a connection may initially be available to + # handle a request, but then become unavailable. + # + # In this case we clear the connection and try again. + pool_request.clear_connection() + else: + break # pragma: nocover + + except BaseException as exc: + with self._optional_thread_lock: + # For any exception or cancellation we remove the request from + # the queue, and then re-assign requests to connections. + self._requests.remove(pool_request) + closing = self._assign_requests_to_connections() + + await self._close_connections(closing) + raise exc from None + + # Return the response. Note that in this case we still have to manage + # the point at which the response is closed. + assert isinstance(response.stream, typing.AsyncIterable) + return Response( + status=response.status, + headers=response.headers, + content=PoolByteStream( + stream=response.stream, pool_request=pool_request, pool=self + ), + extensions=response.extensions, + ) + + def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]: + """ + Manage the state of the connection pool, assigning incoming + requests to connections as available. + + Called whenever a new request is added or removed from the pool. + + Any closing connections are returned, allowing the I/O for closing + those connections to be handled seperately. + """ + closing_connections = [] + + # First we handle cleaning up any connections that are closed, + # have expired their keep-alive, or surplus idle connections. + for connection in list(self._connections): + if connection.is_closed(): + # log: "removing closed connection" + self._connections.remove(connection) + elif connection.has_expired(): + # log: "closing expired connection" + self._connections.remove(connection) + closing_connections.append(connection) + elif ( + connection.is_idle() + and len([connection.is_idle() for connection in self._connections]) + > self._max_keepalive_connections + ): + # log: "closing idle connection" + self._connections.remove(connection) + closing_connections.append(connection) + + # Assign queued requests to connections. + queued_requests = [request for request in self._requests if request.is_queued()] + for pool_request in queued_requests: + origin = pool_request.request.url.origin + available_connections = [ + connection + for connection in self._connections + if connection.can_handle_request(origin) and connection.is_available() + ] + idle_connections = [ + connection for connection in self._connections if connection.is_idle() + ] + + # There are three cases for how we may be able to handle the request: + # + # 1. There is an existing connection that can handle the request. + # 2. We can create a new connection to handle the request. + # 3. We can close an idle connection and then create a new connection + # to handle the request. + if available_connections: + # log: "reusing existing connection" + connection = available_connections[0] + pool_request.assign_to_connection(connection) + elif len(self._connections) < self._max_connections: + # log: "creating new connection" + connection = self.create_connection(origin) + self._connections.append(connection) + pool_request.assign_to_connection(connection) + elif idle_connections: + # log: "closing idle connection" + connection = idle_connections[0] + self._connections.remove(connection) + closing_connections.append(connection) + # log: "creating new connection" + connection = self.create_connection(origin) + self._connections.append(connection) + pool_request.assign_to_connection(connection) + + return closing_connections + + async def _close_connections(self, closing: list[AsyncConnectionInterface]) -> None: + # Close connections which have been removed from the pool. + with AsyncShieldCancellation(): + for connection in closing: + await connection.aclose() + + async def aclose(self) -> None: + # Explicitly close the connection pool. + # Clears all existing requests and connections. + with self._optional_thread_lock: + closing_connections = list(self._connections) + self._connections = [] + await self._close_connections(closing_connections) + + async def __aenter__(self) -> AsyncConnectionPool: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + await self.aclose() + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + with self._optional_thread_lock: + request_is_queued = [request.is_queued() for request in self._requests] + connection_is_idle = [ + connection.is_idle() for connection in self._connections + ] + + num_active_requests = request_is_queued.count(False) + num_queued_requests = request_is_queued.count(True) + num_active_connections = connection_is_idle.count(False) + num_idle_connections = connection_is_idle.count(True) + + requests_info = ( + f"Requests: {num_active_requests} active, {num_queued_requests} queued" + ) + connection_info = ( + f"Connections: {num_active_connections} active, {num_idle_connections} idle" + ) + + return f"<{class_name} [{requests_info} | {connection_info}]>" + + +class PoolByteStream: + def __init__( + self, + stream: typing.AsyncIterable[bytes], + pool_request: AsyncPoolRequest, + pool: AsyncConnectionPool, + ) -> None: + self._stream = stream + self._pool_request = pool_request + self._pool = pool + self._closed = False + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + try: + async for part in self._stream: + yield part + except BaseException as exc: + await self.aclose() + raise exc from None + + async def aclose(self) -> None: + if not self._closed: + self._closed = True + with AsyncShieldCancellation(): + if hasattr(self._stream, "aclose"): + await self._stream.aclose() + + with self._pool._optional_thread_lock: + self._pool._requests.remove(self._pool_request) + closing = self._pool._assign_requests_to_connections() + + await self._pool._close_connections(closing) diff --git a/.venv/Lib/site-packages/httpcore/_async/http11.py b/.venv/Lib/site-packages/httpcore/_async/http11.py new file mode 100644 index 0000000..e6d6d70 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_async/http11.py @@ -0,0 +1,379 @@ +from __future__ import annotations + +import enum +import logging +import ssl +import time +import types +import typing + +import h11 + +from .._backends.base import AsyncNetworkStream +from .._exceptions import ( + ConnectionNotAvailable, + LocalProtocolError, + RemoteProtocolError, + WriteError, + map_exceptions, +) +from .._models import Origin, Request, Response +from .._synchronization import AsyncLock, AsyncShieldCancellation +from .._trace import Trace +from .interfaces import AsyncConnectionInterface + +logger = logging.getLogger("httpcore.http11") + + +# A subset of `h11.Event` types supported by `_send_event` +H11SendEvent = typing.Union[ + h11.Request, + h11.Data, + h11.EndOfMessage, +] + + +class HTTPConnectionState(enum.IntEnum): + NEW = 0 + ACTIVE = 1 + IDLE = 2 + CLOSED = 3 + + +class AsyncHTTP11Connection(AsyncConnectionInterface): + READ_NUM_BYTES = 64 * 1024 + MAX_INCOMPLETE_EVENT_SIZE = 100 * 1024 + + def __init__( + self, + origin: Origin, + stream: AsyncNetworkStream, + keepalive_expiry: float | None = None, + ) -> None: + self._origin = origin + self._network_stream = stream + self._keepalive_expiry: float | None = keepalive_expiry + self._expire_at: float | None = None + self._state = HTTPConnectionState.NEW + self._state_lock = AsyncLock() + self._request_count = 0 + self._h11_state = h11.Connection( + our_role=h11.CLIENT, + max_incomplete_event_size=self.MAX_INCOMPLETE_EVENT_SIZE, + ) + + async def handle_async_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection " + f"to {self._origin}" + ) + + async with self._state_lock: + if self._state in (HTTPConnectionState.NEW, HTTPConnectionState.IDLE): + self._request_count += 1 + self._state = HTTPConnectionState.ACTIVE + self._expire_at = None + else: + raise ConnectionNotAvailable() + + try: + kwargs = {"request": request} + try: + async with Trace( + "send_request_headers", logger, request, kwargs + ) as trace: + await self._send_request_headers(**kwargs) + async with Trace("send_request_body", logger, request, kwargs) as trace: + await self._send_request_body(**kwargs) + except WriteError: + # If we get a write error while we're writing the request, + # then we supress this error and move on to attempting to + # read the response. Servers can sometimes close the request + # pre-emptively and then respond with a well formed HTTP + # error response. + pass + + async with Trace( + "receive_response_headers", logger, request, kwargs + ) as trace: + ( + http_version, + status, + reason_phrase, + headers, + trailing_data, + ) = await self._receive_response_headers(**kwargs) + trace.return_value = ( + http_version, + status, + reason_phrase, + headers, + ) + + network_stream = self._network_stream + + # CONNECT or Upgrade request + if (status == 101) or ( + (request.method == b"CONNECT") and (200 <= status < 300) + ): + network_stream = AsyncHTTP11UpgradeStream(network_stream, trailing_data) + + return Response( + status=status, + headers=headers, + content=HTTP11ConnectionByteStream(self, request), + extensions={ + "http_version": http_version, + "reason_phrase": reason_phrase, + "network_stream": network_stream, + }, + ) + except BaseException as exc: + with AsyncShieldCancellation(): + async with Trace("response_closed", logger, request) as trace: + await self._response_closed() + raise exc + + # Sending the request... + + async def _send_request_headers(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + with map_exceptions({h11.LocalProtocolError: LocalProtocolError}): + event = h11.Request( + method=request.method, + target=request.url.target, + headers=request.headers, + ) + await self._send_event(event, timeout=timeout) + + async def _send_request_body(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + assert isinstance(request.stream, typing.AsyncIterable) + async for chunk in request.stream: + event = h11.Data(data=chunk) + await self._send_event(event, timeout=timeout) + + await self._send_event(h11.EndOfMessage(), timeout=timeout) + + async def _send_event(self, event: h11.Event, timeout: float | None = None) -> None: + bytes_to_send = self._h11_state.send(event) + if bytes_to_send is not None: + await self._network_stream.write(bytes_to_send, timeout=timeout) + + # Receiving the response... + + async def _receive_response_headers( + self, request: Request + ) -> tuple[bytes, int, bytes, list[tuple[bytes, bytes]], bytes]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + while True: + event = await self._receive_event(timeout=timeout) + if isinstance(event, h11.Response): + break + if ( + isinstance(event, h11.InformationalResponse) + and event.status_code == 101 + ): + break + + http_version = b"HTTP/" + event.http_version + + # h11 version 0.11+ supports a `raw_items` interface to get the + # raw header casing, rather than the enforced lowercase headers. + headers = event.headers.raw_items() + + trailing_data, _ = self._h11_state.trailing_data + + return http_version, event.status_code, event.reason, headers, trailing_data + + async def _receive_response_body( + self, request: Request + ) -> typing.AsyncIterator[bytes]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + while True: + event = await self._receive_event(timeout=timeout) + if isinstance(event, h11.Data): + yield bytes(event.data) + elif isinstance(event, (h11.EndOfMessage, h11.PAUSED)): + break + + async def _receive_event( + self, timeout: float | None = None + ) -> h11.Event | type[h11.PAUSED]: + while True: + with map_exceptions({h11.RemoteProtocolError: RemoteProtocolError}): + event = self._h11_state.next_event() + + if event is h11.NEED_DATA: + data = await self._network_stream.read( + self.READ_NUM_BYTES, timeout=timeout + ) + + # If we feed this case through h11 we'll raise an exception like: + # + # httpcore.RemoteProtocolError: can't handle event type + # ConnectionClosed when role=SERVER and state=SEND_RESPONSE + # + # Which is accurate, but not very informative from an end-user + # perspective. Instead we handle this case distinctly and treat + # it as a ConnectError. + if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE: + msg = "Server disconnected without sending a response." + raise RemoteProtocolError(msg) + + self._h11_state.receive_data(data) + else: + # mypy fails to narrow the type in the above if statement above + return event # type: ignore[return-value] + + async def _response_closed(self) -> None: + async with self._state_lock: + if ( + self._h11_state.our_state is h11.DONE + and self._h11_state.their_state is h11.DONE + ): + self._state = HTTPConnectionState.IDLE + self._h11_state.start_next_cycle() + if self._keepalive_expiry is not None: + now = time.monotonic() + self._expire_at = now + self._keepalive_expiry + else: + await self.aclose() + + # Once the connection is no longer required... + + async def aclose(self) -> None: + # Note that this method unilaterally closes the connection, and does + # not have any kind of locking in place around it. + self._state = HTTPConnectionState.CLOSED + await self._network_stream.aclose() + + # The AsyncConnectionInterface methods provide information about the state of + # the connection, allowing for a connection pooling implementation to + # determine when to reuse and when to close the connection... + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def is_available(self) -> bool: + # Note that HTTP/1.1 connections in the "NEW" state are not treated as + # being "available". The control flow which created the connection will + # be able to send an outgoing request, but the connection will not be + # acquired from the connection pool for any other request. + return self._state == HTTPConnectionState.IDLE + + def has_expired(self) -> bool: + now = time.monotonic() + keepalive_expired = self._expire_at is not None and now > self._expire_at + + # If the HTTP connection is idle but the socket is readable, then the + # only valid state is that the socket is about to return b"", indicating + # a server-initiated disconnect. + server_disconnected = ( + self._state == HTTPConnectionState.IDLE + and self._network_stream.get_extra_info("is_readable") + ) + + return keepalive_expired or server_disconnected + + def is_idle(self) -> bool: + return self._state == HTTPConnectionState.IDLE + + def is_closed(self) -> bool: + return self._state == HTTPConnectionState.CLOSED + + def info(self) -> str: + origin = str(self._origin) + return ( + f"{origin!r}, HTTP/1.1, {self._state.name}, " + f"Request Count: {self._request_count}" + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + origin = str(self._origin) + return ( + f"<{class_name} [{origin!r}, {self._state.name}, " + f"Request Count: {self._request_count}]>" + ) + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + async def __aenter__(self) -> AsyncHTTP11Connection: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + await self.aclose() + + +class HTTP11ConnectionByteStream: + def __init__(self, connection: AsyncHTTP11Connection, request: Request) -> None: + self._connection = connection + self._request = request + self._closed = False + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + kwargs = {"request": self._request} + try: + async with Trace("receive_response_body", logger, self._request, kwargs): + async for chunk in self._connection._receive_response_body(**kwargs): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + with AsyncShieldCancellation(): + await self.aclose() + raise exc + + async def aclose(self) -> None: + if not self._closed: + self._closed = True + async with Trace("response_closed", logger, self._request): + await self._connection._response_closed() + + +class AsyncHTTP11UpgradeStream(AsyncNetworkStream): + def __init__(self, stream: AsyncNetworkStream, leading_data: bytes) -> None: + self._stream = stream + self._leading_data = leading_data + + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + if self._leading_data: + buffer = self._leading_data[:max_bytes] + self._leading_data = self._leading_data[max_bytes:] + return buffer + else: + return await self._stream.read(max_bytes, timeout) + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + await self._stream.write(buffer, timeout) + + async def aclose(self) -> None: + await self._stream.aclose() + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + return await self._stream.start_tls(ssl_context, server_hostname, timeout) + + def get_extra_info(self, info: str) -> typing.Any: + return self._stream.get_extra_info(info) diff --git a/.venv/Lib/site-packages/httpcore/_async/http2.py b/.venv/Lib/site-packages/httpcore/_async/http2.py new file mode 100644 index 0000000..dbd0bee --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_async/http2.py @@ -0,0 +1,592 @@ +from __future__ import annotations + +import enum +import logging +import time +import types +import typing + +import h2.config +import h2.connection +import h2.events +import h2.exceptions +import h2.settings + +from .._backends.base import AsyncNetworkStream +from .._exceptions import ( + ConnectionNotAvailable, + LocalProtocolError, + RemoteProtocolError, +) +from .._models import Origin, Request, Response +from .._synchronization import AsyncLock, AsyncSemaphore, AsyncShieldCancellation +from .._trace import Trace +from .interfaces import AsyncConnectionInterface + +logger = logging.getLogger("httpcore.http2") + + +def has_body_headers(request: Request) -> bool: + return any( + k.lower() == b"content-length" or k.lower() == b"transfer-encoding" + for k, v in request.headers + ) + + +class HTTPConnectionState(enum.IntEnum): + ACTIVE = 1 + IDLE = 2 + CLOSED = 3 + + +class AsyncHTTP2Connection(AsyncConnectionInterface): + READ_NUM_BYTES = 64 * 1024 + CONFIG = h2.config.H2Configuration(validate_inbound_headers=False) + + def __init__( + self, + origin: Origin, + stream: AsyncNetworkStream, + keepalive_expiry: float | None = None, + ): + self._origin = origin + self._network_stream = stream + self._keepalive_expiry: float | None = keepalive_expiry + self._h2_state = h2.connection.H2Connection(config=self.CONFIG) + self._state = HTTPConnectionState.IDLE + self._expire_at: float | None = None + self._request_count = 0 + self._init_lock = AsyncLock() + self._state_lock = AsyncLock() + self._read_lock = AsyncLock() + self._write_lock = AsyncLock() + self._sent_connection_init = False + self._used_all_stream_ids = False + self._connection_error = False + + # Mapping from stream ID to response stream events. + self._events: dict[ + int, + list[ + h2.events.ResponseReceived + | h2.events.DataReceived + | h2.events.StreamEnded + | h2.events.StreamReset, + ], + ] = {} + + # Connection terminated events are stored as state since + # we need to handle them for all streams. + self._connection_terminated: h2.events.ConnectionTerminated | None = None + + self._read_exception: Exception | None = None + self._write_exception: Exception | None = None + + async def handle_async_request(self, request: Request) -> Response: + if not self.can_handle_request(request.url.origin): + # This cannot occur in normal operation, since the connection pool + # will only send requests on connections that handle them. + # It's in place simply for resilience as a guard against incorrect + # usage, for anyone working directly with httpcore connections. + raise RuntimeError( + f"Attempted to send request to {request.url.origin} on connection " + f"to {self._origin}" + ) + + async with self._state_lock: + if self._state in (HTTPConnectionState.ACTIVE, HTTPConnectionState.IDLE): + self._request_count += 1 + self._expire_at = None + self._state = HTTPConnectionState.ACTIVE + else: + raise ConnectionNotAvailable() + + async with self._init_lock: + if not self._sent_connection_init: + try: + sci_kwargs = {"request": request} + async with Trace( + "send_connection_init", logger, request, sci_kwargs + ): + await self._send_connection_init(**sci_kwargs) + except BaseException as exc: + with AsyncShieldCancellation(): + await self.aclose() + raise exc + + self._sent_connection_init = True + + # Initially start with just 1 until the remote server provides + # its max_concurrent_streams value + self._max_streams = 1 + + local_settings_max_streams = ( + self._h2_state.local_settings.max_concurrent_streams + ) + self._max_streams_semaphore = AsyncSemaphore(local_settings_max_streams) + + for _ in range(local_settings_max_streams - self._max_streams): + await self._max_streams_semaphore.acquire() + + await self._max_streams_semaphore.acquire() + + try: + stream_id = self._h2_state.get_next_available_stream_id() + self._events[stream_id] = [] + except h2.exceptions.NoAvailableStreamIDError: # pragma: nocover + self._used_all_stream_ids = True + self._request_count -= 1 + raise ConnectionNotAvailable() + + try: + kwargs = {"request": request, "stream_id": stream_id} + async with Trace("send_request_headers", logger, request, kwargs): + await self._send_request_headers(request=request, stream_id=stream_id) + async with Trace("send_request_body", logger, request, kwargs): + await self._send_request_body(request=request, stream_id=stream_id) + async with Trace( + "receive_response_headers", logger, request, kwargs + ) as trace: + status, headers = await self._receive_response( + request=request, stream_id=stream_id + ) + trace.return_value = (status, headers) + + return Response( + status=status, + headers=headers, + content=HTTP2ConnectionByteStream(self, request, stream_id=stream_id), + extensions={ + "http_version": b"HTTP/2", + "network_stream": self._network_stream, + "stream_id": stream_id, + }, + ) + except BaseException as exc: # noqa: PIE786 + with AsyncShieldCancellation(): + kwargs = {"stream_id": stream_id} + async with Trace("response_closed", logger, request, kwargs): + await self._response_closed(stream_id=stream_id) + + if isinstance(exc, h2.exceptions.ProtocolError): + # One case where h2 can raise a protocol error is when a + # closed frame has been seen by the state machine. + # + # This happens when one stream is reading, and encounters + # a GOAWAY event. Other flows of control may then raise + # a protocol error at any point they interact with the 'h2_state'. + # + # In this case we'll have stored the event, and should raise + # it as a RemoteProtocolError. + if self._connection_terminated: # pragma: nocover + raise RemoteProtocolError(self._connection_terminated) + # If h2 raises a protocol error in some other state then we + # must somehow have made a protocol violation. + raise LocalProtocolError(exc) # pragma: nocover + + raise exc + + async def _send_connection_init(self, request: Request) -> None: + """ + The HTTP/2 connection requires some initial setup before we can start + using individual request/response streams on it. + """ + # Need to set these manually here instead of manipulating via + # __setitem__() otherwise the H2Connection will emit SettingsUpdate + # frames in addition to sending the undesired defaults. + self._h2_state.local_settings = h2.settings.Settings( + client=True, + initial_values={ + # Disable PUSH_PROMISE frames from the server since we don't do anything + # with them for now. Maybe when we support caching? + h2.settings.SettingCodes.ENABLE_PUSH: 0, + # These two are taken from h2 for safe defaults + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100, + h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 65536, + }, + ) + + # Some websites (*cough* Yahoo *cough*) balk at this setting being + # present in the initial handshake since it's not defined in the original + # RFC despite the RFC mandating ignoring settings you don't know about. + del self._h2_state.local_settings[ + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL + ] + + self._h2_state.initiate_connection() + self._h2_state.increment_flow_control_window(2**24) + await self._write_outgoing_data(request) + + # Sending the request... + + async def _send_request_headers(self, request: Request, stream_id: int) -> None: + """ + Send the request headers to a given stream ID. + """ + end_stream = not has_body_headers(request) + + # In HTTP/2 the ':authority' pseudo-header is used instead of 'Host'. + # In order to gracefully handle HTTP/1.1 and HTTP/2 we always require + # HTTP/1.1 style headers, and map them appropriately if we end up on + # an HTTP/2 connection. + authority = [v for k, v in request.headers if k.lower() == b"host"][0] + + headers = [ + (b":method", request.method), + (b":authority", authority), + (b":scheme", request.url.scheme), + (b":path", request.url.target), + ] + [ + (k.lower(), v) + for k, v in request.headers + if k.lower() + not in ( + b"host", + b"transfer-encoding", + ) + ] + + self._h2_state.send_headers(stream_id, headers, end_stream=end_stream) + self._h2_state.increment_flow_control_window(2**24, stream_id=stream_id) + await self._write_outgoing_data(request) + + async def _send_request_body(self, request: Request, stream_id: int) -> None: + """ + Iterate over the request body sending it to a given stream ID. + """ + if not has_body_headers(request): + return + + assert isinstance(request.stream, typing.AsyncIterable) + async for data in request.stream: + await self._send_stream_data(request, stream_id, data) + await self._send_end_stream(request, stream_id) + + async def _send_stream_data( + self, request: Request, stream_id: int, data: bytes + ) -> None: + """ + Send a single chunk of data in one or more data frames. + """ + while data: + max_flow = await self._wait_for_outgoing_flow(request, stream_id) + chunk_size = min(len(data), max_flow) + chunk, data = data[:chunk_size], data[chunk_size:] + self._h2_state.send_data(stream_id, chunk) + await self._write_outgoing_data(request) + + async def _send_end_stream(self, request: Request, stream_id: int) -> None: + """ + Send an empty data frame on on a given stream ID with the END_STREAM flag set. + """ + self._h2_state.end_stream(stream_id) + await self._write_outgoing_data(request) + + # Receiving the response... + + async def _receive_response( + self, request: Request, stream_id: int + ) -> tuple[int, list[tuple[bytes, bytes]]]: + """ + Return the response status code and headers for a given stream ID. + """ + while True: + event = await self._receive_stream_event(request, stream_id) + if isinstance(event, h2.events.ResponseReceived): + break + + status_code = 200 + headers = [] + assert event.headers is not None + for k, v in event.headers: + if k == b":status": + status_code = int(v.decode("ascii", errors="ignore")) + elif not k.startswith(b":"): + headers.append((k, v)) + + return (status_code, headers) + + async def _receive_response_body( + self, request: Request, stream_id: int + ) -> typing.AsyncIterator[bytes]: + """ + Iterator that returns the bytes of the response body for a given stream ID. + """ + while True: + event = await self._receive_stream_event(request, stream_id) + if isinstance(event, h2.events.DataReceived): + assert event.flow_controlled_length is not None + assert event.data is not None + amount = event.flow_controlled_length + self._h2_state.acknowledge_received_data(amount, stream_id) + await self._write_outgoing_data(request) + yield event.data + elif isinstance(event, h2.events.StreamEnded): + break + + async def _receive_stream_event( + self, request: Request, stream_id: int + ) -> h2.events.ResponseReceived | h2.events.DataReceived | h2.events.StreamEnded: + """ + Return the next available event for a given stream ID. + + Will read more data from the network if required. + """ + while not self._events.get(stream_id): + await self._receive_events(request, stream_id) + event = self._events[stream_id].pop(0) + if isinstance(event, h2.events.StreamReset): + raise RemoteProtocolError(event) + return event + + async def _receive_events( + self, request: Request, stream_id: int | None = None + ) -> None: + """ + Read some data from the network until we see one or more events + for a given stream ID. + """ + async with self._read_lock: + if self._connection_terminated is not None: + last_stream_id = self._connection_terminated.last_stream_id + if stream_id and last_stream_id and stream_id > last_stream_id: + self._request_count -= 1 + raise ConnectionNotAvailable() + raise RemoteProtocolError(self._connection_terminated) + + # This conditional is a bit icky. We don't want to block reading if we've + # actually got an event to return for a given stream. We need to do that + # check *within* the atomic read lock. Though it also need to be optional, + # because when we call it from `_wait_for_outgoing_flow` we *do* want to + # block until we've available flow control, event when we have events + # pending for the stream ID we're attempting to send on. + if stream_id is None or not self._events.get(stream_id): + events = await self._read_incoming_data(request) + for event in events: + if isinstance(event, h2.events.RemoteSettingsChanged): + async with Trace( + "receive_remote_settings", logger, request + ) as trace: + await self._receive_remote_settings_change(event) + trace.return_value = event + + elif isinstance( + event, + ( + h2.events.ResponseReceived, + h2.events.DataReceived, + h2.events.StreamEnded, + h2.events.StreamReset, + ), + ): + if event.stream_id in self._events: + self._events[event.stream_id].append(event) + + elif isinstance(event, h2.events.ConnectionTerminated): + self._connection_terminated = event + + await self._write_outgoing_data(request) + + async def _receive_remote_settings_change( + self, event: h2.events.RemoteSettingsChanged + ) -> None: + max_concurrent_streams = event.changed_settings.get( + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS + ) + if max_concurrent_streams: + new_max_streams = min( + max_concurrent_streams.new_value, + self._h2_state.local_settings.max_concurrent_streams, + ) + if new_max_streams and new_max_streams != self._max_streams: + while new_max_streams > self._max_streams: + await self._max_streams_semaphore.release() + self._max_streams += 1 + while new_max_streams < self._max_streams: + await self._max_streams_semaphore.acquire() + self._max_streams -= 1 + + async def _response_closed(self, stream_id: int) -> None: + await self._max_streams_semaphore.release() + del self._events[stream_id] + async with self._state_lock: + if self._connection_terminated and not self._events: + await self.aclose() + + elif self._state == HTTPConnectionState.ACTIVE and not self._events: + self._state = HTTPConnectionState.IDLE + if self._keepalive_expiry is not None: + now = time.monotonic() + self._expire_at = now + self._keepalive_expiry + if self._used_all_stream_ids: # pragma: nocover + await self.aclose() + + async def aclose(self) -> None: + # Note that this method unilaterally closes the connection, and does + # not have any kind of locking in place around it. + self._h2_state.close_connection() + self._state = HTTPConnectionState.CLOSED + await self._network_stream.aclose() + + # Wrappers around network read/write operations... + + async def _read_incoming_data(self, request: Request) -> list[h2.events.Event]: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("read", None) + + if self._read_exception is not None: + raise self._read_exception # pragma: nocover + + try: + data = await self._network_stream.read(self.READ_NUM_BYTES, timeout) + if data == b"": + raise RemoteProtocolError("Server disconnected") + except Exception as exc: + # If we get a network error we should: + # + # 1. Save the exception and just raise it immediately on any future reads. + # (For example, this means that a single read timeout or disconnect will + # immediately close all pending streams. Without requiring multiple + # sequential timeouts.) + # 2. Mark the connection as errored, so that we don't accept any other + # incoming requests. + self._read_exception = exc + self._connection_error = True + raise exc + + events: list[h2.events.Event] = self._h2_state.receive_data(data) + + return events + + async def _write_outgoing_data(self, request: Request) -> None: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("write", None) + + async with self._write_lock: + data_to_send = self._h2_state.data_to_send() + + if self._write_exception is not None: + raise self._write_exception # pragma: nocover + + try: + await self._network_stream.write(data_to_send, timeout) + except Exception as exc: # pragma: nocover + # If we get a network error we should: + # + # 1. Save the exception and just raise it immediately on any future write. + # (For example, this means that a single write timeout or disconnect will + # immediately close all pending streams. Without requiring multiple + # sequential timeouts.) + # 2. Mark the connection as errored, so that we don't accept any other + # incoming requests. + self._write_exception = exc + self._connection_error = True + raise exc + + # Flow control... + + async def _wait_for_outgoing_flow(self, request: Request, stream_id: int) -> int: + """ + Returns the maximum allowable outgoing flow for a given stream. + + If the allowable flow is zero, then waits on the network until + WindowUpdated frames have increased the flow rate. + https://tools.ietf.org/html/rfc7540#section-6.9 + """ + local_flow: int = self._h2_state.local_flow_control_window(stream_id) + max_frame_size: int = self._h2_state.max_outbound_frame_size + flow = min(local_flow, max_frame_size) + while flow == 0: + await self._receive_events(request) + local_flow = self._h2_state.local_flow_control_window(stream_id) + max_frame_size = self._h2_state.max_outbound_frame_size + flow = min(local_flow, max_frame_size) + return flow + + # Interface for connection pooling... + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._origin + + def is_available(self) -> bool: + return ( + self._state != HTTPConnectionState.CLOSED + and not self._connection_error + and not self._used_all_stream_ids + and not ( + self._h2_state.state_machine.state + == h2.connection.ConnectionState.CLOSED + ) + ) + + def has_expired(self) -> bool: + now = time.monotonic() + return self._expire_at is not None and now > self._expire_at + + def is_idle(self) -> bool: + return self._state == HTTPConnectionState.IDLE + + def is_closed(self) -> bool: + return self._state == HTTPConnectionState.CLOSED + + def info(self) -> str: + origin = str(self._origin) + return ( + f"{origin!r}, HTTP/2, {self._state.name}, " + f"Request Count: {self._request_count}" + ) + + def __repr__(self) -> str: + class_name = self.__class__.__name__ + origin = str(self._origin) + return ( + f"<{class_name} [{origin!r}, {self._state.name}, " + f"Request Count: {self._request_count}]>" + ) + + # These context managers are not used in the standard flow, but are + # useful for testing or working with connection instances directly. + + async def __aenter__(self) -> AsyncHTTP2Connection: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None = None, + exc_value: BaseException | None = None, + traceback: types.TracebackType | None = None, + ) -> None: + await self.aclose() + + +class HTTP2ConnectionByteStream: + def __init__( + self, connection: AsyncHTTP2Connection, request: Request, stream_id: int + ) -> None: + self._connection = connection + self._request = request + self._stream_id = stream_id + self._closed = False + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + kwargs = {"request": self._request, "stream_id": self._stream_id} + try: + async with Trace("receive_response_body", logger, self._request, kwargs): + async for chunk in self._connection._receive_response_body( + request=self._request, stream_id=self._stream_id + ): + yield chunk + except BaseException as exc: + # If we get an exception while streaming the response, + # we want to close the response (and possibly the connection) + # before raising that exception. + with AsyncShieldCancellation(): + await self.aclose() + raise exc + + async def aclose(self) -> None: + if not self._closed: + self._closed = True + kwargs = {"stream_id": self._stream_id} + async with Trace("response_closed", logger, self._request, kwargs): + await self._connection._response_closed(stream_id=self._stream_id) diff --git a/.venv/Lib/site-packages/httpcore/_async/http_proxy.py b/.venv/Lib/site-packages/httpcore/_async/http_proxy.py new file mode 100644 index 0000000..cc9d920 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_async/http_proxy.py @@ -0,0 +1,367 @@ +from __future__ import annotations + +import base64 +import logging +import ssl +import typing + +from .._backends.base import SOCKET_OPTION, AsyncNetworkBackend +from .._exceptions import ProxyError +from .._models import ( + URL, + Origin, + Request, + Response, + enforce_bytes, + enforce_headers, + enforce_url, +) +from .._ssl import default_ssl_context +from .._synchronization import AsyncLock +from .._trace import Trace +from .connection import AsyncHTTPConnection +from .connection_pool import AsyncConnectionPool +from .http11 import AsyncHTTP11Connection +from .interfaces import AsyncConnectionInterface + +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] + + +logger = logging.getLogger("httpcore.proxy") + + +def merge_headers( + default_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + override_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, +) -> list[tuple[bytes, bytes]]: + """ + Append default_headers and override_headers, de-duplicating if a key exists + in both cases. + """ + default_headers = [] if default_headers is None else list(default_headers) + override_headers = [] if override_headers is None else list(override_headers) + has_override = set(key.lower() for key, value in override_headers) + default_headers = [ + (key, value) + for key, value in default_headers + if key.lower() not in has_override + ] + return default_headers + override_headers + + +class AsyncHTTPProxy(AsyncConnectionPool): # pragma: nocover + """ + A connection pool that sends requests via an HTTP proxy. + """ + + def __init__( + self, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + local_address: str | None = None, + uds: str | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + proxy_url: The URL to use when connecting to the proxy server. + For example `"http://127.0.0.1:8080/"`. + proxy_auth: Any proxy authentication as a two-tuple of + (username, password). May be either bytes or ascii-only str. + proxy_headers: Any HTTP headers to use for the proxy requests. + For example `{"Proxy-Authorization": "Basic :"}`. + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + proxy_ssl_context: The same as `ssl_context`, but for a proxy server rather than a remote origin. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish + a connection. + local_address: Local address to connect from. Can also be used to + connect using a particular address family. Using + `local_address="0.0.0.0"` will connect using an `AF_INET` address + (IPv4), while using `local_address="::"` will connect using an + `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + """ + super().__init__( + ssl_context=ssl_context, + max_connections=max_connections, + max_keepalive_connections=max_keepalive_connections, + keepalive_expiry=keepalive_expiry, + http1=http1, + http2=http2, + network_backend=network_backend, + retries=retries, + local_address=local_address, + uds=uds, + socket_options=socket_options, + ) + + self._proxy_url = enforce_url(proxy_url, name="proxy_url") + if ( + self._proxy_url.scheme == b"http" and proxy_ssl_context is not None + ): # pragma: no cover + raise RuntimeError( + "The `proxy_ssl_context` argument is not allowed for the http scheme" + ) + + self._ssl_context = ssl_context + self._proxy_ssl_context = proxy_ssl_context + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + if proxy_auth is not None: + username = enforce_bytes(proxy_auth[0], name="proxy_auth") + password = enforce_bytes(proxy_auth[1], name="proxy_auth") + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) + self._proxy_headers = [ + (b"Proxy-Authorization", authorization) + ] + self._proxy_headers + + def create_connection(self, origin: Origin) -> AsyncConnectionInterface: + if origin.scheme == b"http": + return AsyncForwardHTTPConnection( + proxy_origin=self._proxy_url.origin, + proxy_headers=self._proxy_headers, + remote_origin=origin, + keepalive_expiry=self._keepalive_expiry, + network_backend=self._network_backend, + proxy_ssl_context=self._proxy_ssl_context, + ) + return AsyncTunnelHTTPConnection( + proxy_origin=self._proxy_url.origin, + proxy_headers=self._proxy_headers, + remote_origin=origin, + ssl_context=self._ssl_context, + proxy_ssl_context=self._proxy_ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + +class AsyncForwardHTTPConnection(AsyncConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + proxy_headers: HeadersAsMapping | HeadersAsSequence | None = None, + keepalive_expiry: float | None = None, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + ) -> None: + self._connection = AsyncHTTPConnection( + origin=proxy_origin, + keepalive_expiry=keepalive_expiry, + network_backend=network_backend, + socket_options=socket_options, + ssl_context=proxy_ssl_context, + ) + self._proxy_origin = proxy_origin + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + self._remote_origin = remote_origin + + async def handle_async_request(self, request: Request) -> Response: + headers = merge_headers(self._proxy_headers, request.headers) + url = URL( + scheme=self._proxy_origin.scheme, + host=self._proxy_origin.host, + port=self._proxy_origin.port, + target=bytes(request.url), + ) + proxy_request = Request( + method=request.method, + url=url, + headers=headers, + content=request.stream, + extensions=request.extensions, + ) + return await self._connection.handle_async_request(proxy_request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + async def aclose(self) -> None: + await self._connection.aclose() + + def info(self) -> str: + return self._connection.info() + + def is_available(self) -> bool: + return self._connection.is_available() + + def has_expired(self) -> bool: + return self._connection.has_expired() + + def is_idle(self) -> bool: + return self._connection.is_idle() + + def is_closed(self) -> bool: + return self._connection.is_closed() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" + + +class AsyncTunnelHTTPConnection(AsyncConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + ssl_context: ssl.SSLContext | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + proxy_headers: typing.Sequence[tuple[bytes, bytes]] | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + network_backend: AsyncNetworkBackend | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> None: + self._connection: AsyncConnectionInterface = AsyncHTTPConnection( + origin=proxy_origin, + keepalive_expiry=keepalive_expiry, + network_backend=network_backend, + socket_options=socket_options, + ssl_context=proxy_ssl_context, + ) + self._proxy_origin = proxy_origin + self._remote_origin = remote_origin + self._ssl_context = ssl_context + self._proxy_ssl_context = proxy_ssl_context + self._proxy_headers = enforce_headers(proxy_headers, name="proxy_headers") + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + self._connect_lock = AsyncLock() + self._connected = False + + async def handle_async_request(self, request: Request) -> Response: + timeouts = request.extensions.get("timeout", {}) + timeout = timeouts.get("connect", None) + + async with self._connect_lock: + if not self._connected: + target = b"%b:%d" % (self._remote_origin.host, self._remote_origin.port) + + connect_url = URL( + scheme=self._proxy_origin.scheme, + host=self._proxy_origin.host, + port=self._proxy_origin.port, + target=target, + ) + connect_headers = merge_headers( + [(b"Host", target), (b"Accept", b"*/*")], self._proxy_headers + ) + connect_request = Request( + method=b"CONNECT", + url=connect_url, + headers=connect_headers, + extensions=request.extensions, + ) + connect_response = await self._connection.handle_async_request( + connect_request + ) + + if connect_response.status < 200 or connect_response.status > 299: + reason_bytes = connect_response.extensions.get("reason_phrase", b"") + reason_str = reason_bytes.decode("ascii", errors="ignore") + msg = "%d %s" % (connect_response.status, reason_str) + await self._connection.aclose() + raise ProxyError(msg) + + stream = connect_response.extensions["network_stream"] + + # Upgrade the stream to SSL + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + async with Trace("start_tls", logger, request, kwargs) as trace: + stream = await stream.start_tls(**kwargs) + trace.return_value = stream + + # Determine if we should be using HTTP/1.1 or HTTP/2 + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + + # Create the HTTP/1.1 or HTTP/2 connection + if http2_negotiated or (self._http2 and not self._http1): + from .http2 import AsyncHTTP2Connection + + self._connection = AsyncHTTP2Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = AsyncHTTP11Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + + self._connected = True + return await self._connection.handle_async_request(request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + async def aclose(self) -> None: + await self._connection.aclose() + + def info(self) -> str: + return self._connection.info() + + def is_available(self) -> bool: + return self._connection.is_available() + + def has_expired(self) -> bool: + return self._connection.has_expired() + + def is_idle(self) -> bool: + return self._connection.is_idle() + + def is_closed(self) -> bool: + return self._connection.is_closed() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" diff --git a/.venv/Lib/site-packages/httpcore/_async/interfaces.py b/.venv/Lib/site-packages/httpcore/_async/interfaces.py new file mode 100644 index 0000000..361583b --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_async/interfaces.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import contextlib +import typing + +from .._models import ( + URL, + Extensions, + HeaderTypes, + Origin, + Request, + Response, + enforce_bytes, + enforce_headers, + enforce_url, + include_request_headers, +) + + +class AsyncRequestInterface: + async def request( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.AsyncIterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> Response: + # Strict type checking on our parameters. + method = enforce_bytes(method, name="method") + url = enforce_url(url, name="url") + headers = enforce_headers(headers, name="headers") + + # Include Host header, and optionally Content-Length or Transfer-Encoding. + headers = include_request_headers(headers, url=url, content=content) + + request = Request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + response = await self.handle_async_request(request) + try: + await response.aread() + finally: + await response.aclose() + return response + + @contextlib.asynccontextmanager + async def stream( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes | typing.AsyncIterator[bytes] | None = None, + extensions: Extensions | None = None, + ) -> typing.AsyncIterator[Response]: + # Strict type checking on our parameters. + method = enforce_bytes(method, name="method") + url = enforce_url(url, name="url") + headers = enforce_headers(headers, name="headers") + + # Include Host header, and optionally Content-Length or Transfer-Encoding. + headers = include_request_headers(headers, url=url, content=content) + + request = Request( + method=method, + url=url, + headers=headers, + content=content, + extensions=extensions, + ) + response = await self.handle_async_request(request) + try: + yield response + finally: + await response.aclose() + + async def handle_async_request(self, request: Request) -> Response: + raise NotImplementedError() # pragma: nocover + + +class AsyncConnectionInterface(AsyncRequestInterface): + async def aclose(self) -> None: + raise NotImplementedError() # pragma: nocover + + def info(self) -> str: + raise NotImplementedError() # pragma: nocover + + def can_handle_request(self, origin: Origin) -> bool: + raise NotImplementedError() # pragma: nocover + + def is_available(self) -> bool: + """ + Return `True` if the connection is currently able to accept an + outgoing request. + + An HTTP/1.1 connection will only be available if it is currently idle. + + An HTTP/2 connection will be available so long as the stream ID space is + not yet exhausted, and the connection is not in an error state. + + While the connection is being established we may not yet know if it is going + to result in an HTTP/1.1 or HTTP/2 connection. The connection should be + treated as being available, but might ultimately raise `NewConnectionRequired` + required exceptions if multiple requests are attempted over a connection + that ends up being established as HTTP/1.1. + """ + raise NotImplementedError() # pragma: nocover + + def has_expired(self) -> bool: + """ + Return `True` if the connection is in a state where it should be closed. + + This either means that the connection is idle and it has passed the + expiry time on its keep-alive, or that server has sent an EOF. + """ + raise NotImplementedError() # pragma: nocover + + def is_idle(self) -> bool: + """ + Return `True` if the connection is currently idle. + """ + raise NotImplementedError() # pragma: nocover + + def is_closed(self) -> bool: + """ + Return `True` if the connection has been closed. + + Used when a response is closed to determine if the connection may be + returned to the connection pool or not. + """ + raise NotImplementedError() # pragma: nocover diff --git a/.venv/Lib/site-packages/httpcore/_async/socks_proxy.py b/.venv/Lib/site-packages/httpcore/_async/socks_proxy.py new file mode 100644 index 0000000..b363f55 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_async/socks_proxy.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import logging +import ssl + +import socksio + +from .._backends.auto import AutoBackend +from .._backends.base import AsyncNetworkBackend, AsyncNetworkStream +from .._exceptions import ConnectionNotAvailable, ProxyError +from .._models import URL, Origin, Request, Response, enforce_bytes, enforce_url +from .._ssl import default_ssl_context +from .._synchronization import AsyncLock +from .._trace import Trace +from .connection_pool import AsyncConnectionPool +from .http11 import AsyncHTTP11Connection +from .interfaces import AsyncConnectionInterface + +logger = logging.getLogger("httpcore.socks") + + +AUTH_METHODS = { + b"\x00": "NO AUTHENTICATION REQUIRED", + b"\x01": "GSSAPI", + b"\x02": "USERNAME/PASSWORD", + b"\xff": "NO ACCEPTABLE METHODS", +} + +REPLY_CODES = { + b"\x00": "Succeeded", + b"\x01": "General SOCKS server failure", + b"\x02": "Connection not allowed by ruleset", + b"\x03": "Network unreachable", + b"\x04": "Host unreachable", + b"\x05": "Connection refused", + b"\x06": "TTL expired", + b"\x07": "Command not supported", + b"\x08": "Address type not supported", +} + + +async def _init_socks5_connection( + stream: AsyncNetworkStream, + *, + host: bytes, + port: int, + auth: tuple[bytes, bytes] | None = None, +) -> None: + conn = socksio.socks5.SOCKS5Connection() + + # Auth method request + auth_method = ( + socksio.socks5.SOCKS5AuthMethod.NO_AUTH_REQUIRED + if auth is None + else socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD + ) + conn.send(socksio.socks5.SOCKS5AuthMethodsRequest([auth_method])) + outgoing_bytes = conn.data_to_send() + await stream.write(outgoing_bytes) + + # Auth method response + incoming_bytes = await stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5AuthReply) + if response.method != auth_method: + requested = AUTH_METHODS.get(auth_method, "UNKNOWN") + responded = AUTH_METHODS.get(response.method, "UNKNOWN") + raise ProxyError( + f"Requested {requested} from proxy server, but got {responded}." + ) + + if response.method == socksio.socks5.SOCKS5AuthMethod.USERNAME_PASSWORD: + # Username/password request + assert auth is not None + username, password = auth + conn.send(socksio.socks5.SOCKS5UsernamePasswordRequest(username, password)) + outgoing_bytes = conn.data_to_send() + await stream.write(outgoing_bytes) + + # Username/password response + incoming_bytes = await stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5UsernamePasswordReply) + if not response.success: + raise ProxyError("Invalid username/password") + + # Connect request + conn.send( + socksio.socks5.SOCKS5CommandRequest.from_address( + socksio.socks5.SOCKS5Command.CONNECT, (host, port) + ) + ) + outgoing_bytes = conn.data_to_send() + await stream.write(outgoing_bytes) + + # Connect response + incoming_bytes = await stream.read(max_bytes=4096) + response = conn.receive_data(incoming_bytes) + assert isinstance(response, socksio.socks5.SOCKS5Reply) + if response.reply_code != socksio.socks5.SOCKS5ReplyCode.SUCCEEDED: + reply_code = REPLY_CODES.get(response.reply_code, "UNKOWN") + raise ProxyError(f"Proxy Server could not connect: {reply_code}.") + + +class AsyncSOCKSProxy(AsyncConnectionPool): # pragma: nocover + """ + A connection pool that sends requests via an HTTP proxy. + """ + + def __init__( + self, + proxy_url: URL | bytes | str, + proxy_auth: tuple[bytes | str, bytes | str] | None = None, + ssl_context: ssl.SSLContext | None = None, + max_connections: int | None = 10, + max_keepalive_connections: int | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + retries: int = 0, + network_backend: AsyncNetworkBackend | None = None, + ) -> None: + """ + A connection pool for making HTTP requests. + + Parameters: + proxy_url: The URL to use when connecting to the proxy server. + For example `"http://127.0.0.1:8080/"`. + ssl_context: An SSL context to use for verifying connections. + If not specified, the default `httpcore.default_ssl_context()` + will be used. + max_connections: The maximum number of concurrent HTTP connections that + the pool should allow. Any attempt to send a request on a pool that + would exceed this amount will block until a connection is available. + max_keepalive_connections: The maximum number of idle HTTP connections + that will be maintained in the pool. + keepalive_expiry: The duration in seconds that an idle HTTP connection + may be maintained for before being expired from the pool. + http1: A boolean indicating if HTTP/1.1 requests should be supported + by the connection pool. Defaults to True. + http2: A boolean indicating if HTTP/2 requests should be supported by + the connection pool. Defaults to False. + retries: The maximum number of retries when trying to establish + a connection. + local_address: Local address to connect from. Can also be used to + connect using a particular address family. Using + `local_address="0.0.0.0"` will connect using an `AF_INET` address + (IPv4), while using `local_address="::"` will connect using an + `AF_INET6` address (IPv6). + uds: Path to a Unix Domain Socket to use instead of TCP sockets. + network_backend: A backend instance to use for handling network I/O. + """ + super().__init__( + ssl_context=ssl_context, + max_connections=max_connections, + max_keepalive_connections=max_keepalive_connections, + keepalive_expiry=keepalive_expiry, + http1=http1, + http2=http2, + network_backend=network_backend, + retries=retries, + ) + self._ssl_context = ssl_context + self._proxy_url = enforce_url(proxy_url, name="proxy_url") + if proxy_auth is not None: + username, password = proxy_auth + username_bytes = enforce_bytes(username, name="proxy_auth") + password_bytes = enforce_bytes(password, name="proxy_auth") + self._proxy_auth: tuple[bytes, bytes] | None = ( + username_bytes, + password_bytes, + ) + else: + self._proxy_auth = None + + def create_connection(self, origin: Origin) -> AsyncConnectionInterface: + return AsyncSocks5Connection( + proxy_origin=self._proxy_url.origin, + remote_origin=origin, + proxy_auth=self._proxy_auth, + ssl_context=self._ssl_context, + keepalive_expiry=self._keepalive_expiry, + http1=self._http1, + http2=self._http2, + network_backend=self._network_backend, + ) + + +class AsyncSocks5Connection(AsyncConnectionInterface): + def __init__( + self, + proxy_origin: Origin, + remote_origin: Origin, + proxy_auth: tuple[bytes, bytes] | None = None, + ssl_context: ssl.SSLContext | None = None, + keepalive_expiry: float | None = None, + http1: bool = True, + http2: bool = False, + network_backend: AsyncNetworkBackend | None = None, + ) -> None: + self._proxy_origin = proxy_origin + self._remote_origin = remote_origin + self._proxy_auth = proxy_auth + self._ssl_context = ssl_context + self._keepalive_expiry = keepalive_expiry + self._http1 = http1 + self._http2 = http2 + + self._network_backend: AsyncNetworkBackend = ( + AutoBackend() if network_backend is None else network_backend + ) + self._connect_lock = AsyncLock() + self._connection: AsyncConnectionInterface | None = None + self._connect_failed = False + + async def handle_async_request(self, request: Request) -> Response: + timeouts = request.extensions.get("timeout", {}) + sni_hostname = request.extensions.get("sni_hostname", None) + timeout = timeouts.get("connect", None) + + async with self._connect_lock: + if self._connection is None: + try: + # Connect to the proxy + kwargs = { + "host": self._proxy_origin.host.decode("ascii"), + "port": self._proxy_origin.port, + "timeout": timeout, + } + async with Trace("connect_tcp", logger, request, kwargs) as trace: + stream = await self._network_backend.connect_tcp(**kwargs) + trace.return_value = stream + + # Connect to the remote host using socks5 + kwargs = { + "stream": stream, + "host": self._remote_origin.host.decode("ascii"), + "port": self._remote_origin.port, + "auth": self._proxy_auth, + } + async with Trace( + "setup_socks5_connection", logger, request, kwargs + ) as trace: + await _init_socks5_connection(**kwargs) + trace.return_value = stream + + # Upgrade the stream to SSL + if self._remote_origin.scheme == b"https": + ssl_context = ( + default_ssl_context() + if self._ssl_context is None + else self._ssl_context + ) + alpn_protocols = ( + ["http/1.1", "h2"] if self._http2 else ["http/1.1"] + ) + ssl_context.set_alpn_protocols(alpn_protocols) + + kwargs = { + "ssl_context": ssl_context, + "server_hostname": sni_hostname + or self._remote_origin.host.decode("ascii"), + "timeout": timeout, + } + async with Trace("start_tls", logger, request, kwargs) as trace: + stream = await stream.start_tls(**kwargs) + trace.return_value = stream + + # Determine if we should be using HTTP/1.1 or HTTP/2 + ssl_object = stream.get_extra_info("ssl_object") + http2_negotiated = ( + ssl_object is not None + and ssl_object.selected_alpn_protocol() == "h2" + ) + + # Create the HTTP/1.1 or HTTP/2 connection + if http2_negotiated or ( + self._http2 and not self._http1 + ): # pragma: nocover + from .http2 import AsyncHTTP2Connection + + self._connection = AsyncHTTP2Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + else: + self._connection = AsyncHTTP11Connection( + origin=self._remote_origin, + stream=stream, + keepalive_expiry=self._keepalive_expiry, + ) + except Exception as exc: + self._connect_failed = True + raise exc + elif not self._connection.is_available(): # pragma: nocover + raise ConnectionNotAvailable() + + return await self._connection.handle_async_request(request) + + def can_handle_request(self, origin: Origin) -> bool: + return origin == self._remote_origin + + async def aclose(self) -> None: + if self._connection is not None: + await self._connection.aclose() + + def is_available(self) -> bool: + if self._connection is None: # pragma: nocover + # If HTTP/2 support is enabled, and the resulting connection could + # end up as HTTP/2 then we should indicate the connection as being + # available to service multiple requests. + return ( + self._http2 + and (self._remote_origin.scheme == b"https" or not self._http1) + and not self._connect_failed + ) + return self._connection.is_available() + + def has_expired(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.has_expired() + + def is_idle(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.is_idle() + + def is_closed(self) -> bool: + if self._connection is None: # pragma: nocover + return self._connect_failed + return self._connection.is_closed() + + def info(self) -> str: + if self._connection is None: # pragma: nocover + return "CONNECTION FAILED" if self._connect_failed else "CONNECTING" + return self._connection.info() + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.info()}]>" diff --git a/.venv/Lib/site-packages/httpcore/_backends/__init__.py b/.venv/Lib/site-packages/httpcore/_backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/Lib/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65cf35c96ff37bad8f7c383407b131b82e336746 GIT binary patch literal 215 zcmX@j%ge<81S^xRGC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!Ds#4q2`x@7Dvk+G z%#CqLEzT~paZ4;NaSZTOC@WTQ%SkNGj`2uL&Q2{-aLLcg zkI^ej%`1!X$xMnV&MZmQEdWX-rl%IiWR#Q?Bc=O6ROh7>>&M4u=4F<|$LkeT h-r}&y%}*)KNwq6t1v-Nfh>JmtkIamWj77{q764f4JQx4~ literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d19940fa474881d0840498f2e0c90959a5168e69 GIT binary patch literal 8559 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 GF8&1*>tT5S literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41e531fc227763fc244be0c1b3c863181b46da38 GIT binary patch literal 4909 zcmcIo-H#hZ67R8R{B3V`y^ya3oQ-qI9>jqI0SP&dkWFBf#mTV=I{A!7vopPk6MM$j zJ)6W94}n|hbWax~q=hGV#ESS!cu9niRs%=~Uf|8ak#gkes=8?pk%g^+b(5lg9)hEh=!>__U6N<=)Pm8f{C z6_u)&g}gw7d;&K!%)vJr$G?^6ZtQPAV{3^Cnp}zC%AZr-m|00h$ScH(t`SSU z6+t@)siXx@1757p%LrZ^c!@qQD|kuZrTV;FD>9e<9>vTlUfQs2$2HuVWAn0!%%DYO zN9ip>XPr<|Q7i%n7`GBuWKDx3j9W=CORK0>%2LT=XlC=?#BlgFwTIAy%LWHhStlwWb3 zW_77%(ZV_AT&AYWtEUa_o;bHqxXKHs>juA4ebq3pP*#|CE<4rIRcc?YF4mT-yynuw zO&BsRQeM5}x=qtz^oYKUvu%qXSvEK=HCvtv8@1X~L;EfvZvu?!fg_U}yb%$@#Dwq$U!hu#5UV}ECR#k32&3Ron9q^0mt~UX9eU&o( zlEYowXi#||r~|ygw=KzCgSom}=k1-tr_vwT1q*t3mN&?~44IgfcO{R>kITSj>A}f!5kd2AFP%jn47-E(e zt=X>F;e*Ud-7(z4TZOV?Q!mD-yUJ`&6H(ELv1ypi=8)i~*g+(RfOsm-2W#<#Jr1N3 zDVOIGY%g>l9g!WTv>wd_5z=7+Ts<*(^M~C`=Pl+bOrztl1Y!C+yZ- z!b|8n-ixk#DP3crON|hoqY!2Or-k6uZfoR{Z**{ap>7-5A+Z?8dQ|AXATuv2Kc_cJ|W1ySGEp zrll6#kO{wjLs)^P^xgnE07?mgmH@>890olrCP5V6t$4tdPXJg6t4Rr~0GTN-=ljIV z5?RV-OlXy4ycmi3eu(klsZEDTfAO+)$297?VOflF?&Y`xAm-{$Q)CB@3Nc^;uT4XI z*#MThYc|_^hRrL5#ty(b?#IoJk+{JS3EFs@@B>|K8tx_ObH_mtM}Y$q^MM(wcI~<@ z_tT!;#?;W9BOrx6ibQ}QS{5|CZTKm@5_HzdAU$0U&+=!#rQGrXR0(ikF5;<}kUWjo zDQyOz5a$$ZwRjj8*gS}7Pmh=@mOTd}96cPeMk4c{)W1_VpZh$qSD4$0mLXg->?jPg zr;r>&@&_b?NPhue#Xj-e9vtuka>1p{SgzB*FP%Mk=G1xp?78y`XUlH}!7U&GBY+(S z0+{d;_JxvxXcWj^Mbe)R2yhx0o+cnEge)=kC!F{+l4p?o8OaGGxckxQeinI1ityuW zK-LIE>AnLS#X}n}&3Cg2i0Is8SA!=+^u$y*jy)OC*h`UJv)wfIGGzCQVtzM=Q9gmZ zJtzsp2w*@l5*Ppud|-ejI5rFnGZGlktgV6Jcc`GIPVkm(`mV8E8TC)}Q*`4qL>cnc zK`He~^qMkA+6-&qp;AHMZhPluZK8O=o9-bpe@Ffss$YKWsla>LGrK%L$b%KgyFC#j7ZK3)L*+Qx2Y!)u+xxaqJzS`~ z21{5G2^t}t408edBa$bO_#lFX1w%X))r3T)0h|aB5yGB?DS$a6h=KkmD}yEMZCe=# z5l9gFeuK(jX0$Ru*(Ut@4Ner>?_3$=!#A;Qc`&}yJrAajCVc_V?Z>vz7|!^w)tIB7s|| zyN^=r6?h#pWrs4X@a)4V;%6mfF&!&ob_PiSi3m|FDA?Zu@>l5mhADPt{1iKgu%a{& zZJV3GuY{FV_?UsO1cosN9~QjMPGT=6>ljbFV8g#OJnj4{{DVN&KKQBQCGqRWV9aQF z*`X3gemIEwLY$8{h5{@Z9F3PaEm3iT zXWq?hsHuM~zP)%ef75*b>6?$-Q6CF)p8fFn2gg6wK5pGU^!%N@M{iFay`vrrPk82| v{O!r&9rZw0i)aV$M%Bs4jqLqgUYWj2fZTs#TA8^^fZQ*IdBq=qC(rADAfwvb literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0878aad90f4a66cf20d460b003786db2c9cec46 GIT binary patch literal 7173 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 PC0mvU@Bf&Z#47z47#yMu literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..715bee80ea3bc7b02bf10e0e9163743224328e7c GIT binary patch literal 11380 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 WTUQ7^R}NYp=E5^QegjLL?IU}1gAL`7= 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 S=b4x2uAHa$zbW`7>-}HsvID>X literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_backends/anyio.py b/.venv/Lib/site-packages/httpcore/_backends/anyio.py new file mode 100644 index 0000000..a140095 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_backends/anyio.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import ssl +import typing + +import anyio + +from .._exceptions import ( + ConnectError, + ConnectTimeout, + ReadError, + ReadTimeout, + WriteError, + WriteTimeout, + map_exceptions, +) +from .._utils import is_socket_readable +from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream + + +class AnyIOStream(AsyncNetworkStream): + def __init__(self, stream: anyio.abc.ByteStream) -> None: + self._stream = stream + + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + exc_map = { + TimeoutError: ReadTimeout, + anyio.BrokenResourceError: ReadError, + anyio.ClosedResourceError: ReadError, + anyio.EndOfStream: ReadError, + } + with map_exceptions(exc_map): + with anyio.fail_after(timeout): + try: + return await self._stream.receive(max_bytes=max_bytes) + except anyio.EndOfStream: # pragma: nocover + return b"" + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + if not buffer: + return + + exc_map = { + TimeoutError: WriteTimeout, + anyio.BrokenResourceError: WriteError, + anyio.ClosedResourceError: WriteError, + } + with map_exceptions(exc_map): + with anyio.fail_after(timeout): + await self._stream.send(item=buffer) + + async def aclose(self) -> None: + await self._stream.aclose() + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + exc_map = { + TimeoutError: ConnectTimeout, + anyio.BrokenResourceError: ConnectError, + anyio.EndOfStream: ConnectError, + ssl.SSLError: ConnectError, + } + with map_exceptions(exc_map): + try: + with anyio.fail_after(timeout): + ssl_stream = await anyio.streams.tls.TLSStream.wrap( + self._stream, + ssl_context=ssl_context, + hostname=server_hostname, + standard_compatible=False, + server_side=False, + ) + except Exception as exc: # pragma: nocover + await self.aclose() + raise exc + return AnyIOStream(ssl_stream) + + def get_extra_info(self, info: str) -> typing.Any: + if info == "ssl_object": + return self._stream.extra(anyio.streams.tls.TLSAttribute.ssl_object, None) + if info == "client_addr": + return self._stream.extra(anyio.abc.SocketAttribute.local_address, None) + if info == "server_addr": + return self._stream.extra(anyio.abc.SocketAttribute.remote_address, None) + if info == "socket": + return self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None) + if info == "is_readable": + sock = self._stream.extra(anyio.abc.SocketAttribute.raw_socket, None) + return is_socket_readable(sock) + return None + + +class AnyIOBackend(AsyncNetworkBackend): + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: # pragma: nocover + if socket_options is None: + socket_options = [] + exc_map = { + TimeoutError: ConnectTimeout, + OSError: ConnectError, + anyio.BrokenResourceError: ConnectError, + } + with map_exceptions(exc_map): + with anyio.fail_after(timeout): + stream: anyio.abc.ByteStream = await anyio.connect_tcp( + remote_host=host, + remote_port=port, + local_host=local_address, + ) + # By default TCP sockets opened in `asyncio` include TCP_NODELAY. + for option in socket_options: + stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover + return AnyIOStream(stream) + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: # pragma: nocover + if socket_options is None: + socket_options = [] + exc_map = { + TimeoutError: ConnectTimeout, + OSError: ConnectError, + anyio.BrokenResourceError: ConnectError, + } + with map_exceptions(exc_map): + with anyio.fail_after(timeout): + stream: anyio.abc.ByteStream = await anyio.connect_unix(path) + for option in socket_options: + stream._raw_socket.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover + return AnyIOStream(stream) + + async def sleep(self, seconds: float) -> None: + await anyio.sleep(seconds) # pragma: nocover diff --git a/.venv/Lib/site-packages/httpcore/_backends/auto.py b/.venv/Lib/site-packages/httpcore/_backends/auto.py new file mode 100644 index 0000000..49f0e69 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_backends/auto.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import typing + +from .._synchronization import current_async_library +from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream + + +class AutoBackend(AsyncNetworkBackend): + async def _init_backend(self) -> None: + if not (hasattr(self, "_backend")): + backend = current_async_library() + if backend == "trio": + from .trio import TrioBackend + + self._backend: AsyncNetworkBackend = TrioBackend() + else: + from .anyio import AnyIOBackend + + self._backend = AnyIOBackend() + + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + await self._init_backend() + return await self._backend.connect_tcp( + host, + port, + timeout=timeout, + local_address=local_address, + socket_options=socket_options, + ) + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: # pragma: nocover + await self._init_backend() + return await self._backend.connect_unix_socket( + path, timeout=timeout, socket_options=socket_options + ) + + async def sleep(self, seconds: float) -> None: # pragma: nocover + await self._init_backend() + return await self._backend.sleep(seconds) diff --git a/.venv/Lib/site-packages/httpcore/_backends/base.py b/.venv/Lib/site-packages/httpcore/_backends/base.py new file mode 100644 index 0000000..cf55c8b --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_backends/base.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import ssl +import time +import typing + +SOCKET_OPTION = typing.Union[ + typing.Tuple[int, int, int], + typing.Tuple[int, int, typing.Union[bytes, bytearray]], + typing.Tuple[int, int, None, int], +] + + +class NetworkStream: + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + raise NotImplementedError() # pragma: nocover + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + raise NotImplementedError() # pragma: nocover + + def close(self) -> None: + raise NotImplementedError() # pragma: nocover + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + raise NotImplementedError() # pragma: nocover + + def get_extra_info(self, info: str) -> typing.Any: + return None # pragma: nocover + + +class NetworkBackend: + def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + raise NotImplementedError() # pragma: nocover + + def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + raise NotImplementedError() # pragma: nocover + + def sleep(self, seconds: float) -> None: + time.sleep(seconds) # pragma: nocover + + +class AsyncNetworkStream: + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + raise NotImplementedError() # pragma: nocover + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + raise NotImplementedError() # pragma: nocover + + async def aclose(self) -> None: + raise NotImplementedError() # pragma: nocover + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + raise NotImplementedError() # pragma: nocover + + def get_extra_info(self, info: str) -> typing.Any: + return None # pragma: nocover + + +class AsyncNetworkBackend: + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + raise NotImplementedError() # pragma: nocover + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + raise NotImplementedError() # pragma: nocover + + async def sleep(self, seconds: float) -> None: + raise NotImplementedError() # pragma: nocover diff --git a/.venv/Lib/site-packages/httpcore/_backends/mock.py b/.venv/Lib/site-packages/httpcore/_backends/mock.py new file mode 100644 index 0000000..9b6edca --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_backends/mock.py @@ -0,0 +1,143 @@ +from __future__ import annotations + +import ssl +import typing + +from .._exceptions import ReadError +from .base import ( + SOCKET_OPTION, + AsyncNetworkBackend, + AsyncNetworkStream, + NetworkBackend, + NetworkStream, +) + + +class MockSSLObject: + def __init__(self, http2: bool): + self._http2 = http2 + + def selected_alpn_protocol(self) -> str: + return "h2" if self._http2 else "http/1.1" + + +class MockStream(NetworkStream): + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: + self._buffer = buffer + self._http2 = http2 + self._closed = False + + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + if self._closed: + raise ReadError("Connection closed") + if not self._buffer: + return b"" + return self._buffer.pop(0) + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + pass + + def close(self) -> None: + self._closed = True + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + return self + + def get_extra_info(self, info: str) -> typing.Any: + return MockSSLObject(http2=self._http2) if info == "ssl_object" else None + + def __repr__(self) -> str: + return "" + + +class MockBackend(NetworkBackend): + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: + self._buffer = buffer + self._http2 = http2 + + def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + return MockStream(list(self._buffer), http2=self._http2) + + def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + return MockStream(list(self._buffer), http2=self._http2) + + def sleep(self, seconds: float) -> None: + pass + + +class AsyncMockStream(AsyncNetworkStream): + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: + self._buffer = buffer + self._http2 = http2 + self._closed = False + + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + if self._closed: + raise ReadError("Connection closed") + if not self._buffer: + return b"" + return self._buffer.pop(0) + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + pass + + async def aclose(self) -> None: + self._closed = True + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + return self + + def get_extra_info(self, info: str) -> typing.Any: + return MockSSLObject(http2=self._http2) if info == "ssl_object" else None + + def __repr__(self) -> str: + return "" + + +class AsyncMockBackend(AsyncNetworkBackend): + def __init__(self, buffer: list[bytes], http2: bool = False) -> None: + self._buffer = buffer + self._http2 = http2 + + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + return AsyncMockStream(list(self._buffer), http2=self._http2) + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + return AsyncMockStream(list(self._buffer), http2=self._http2) + + async def sleep(self, seconds: float) -> None: + pass diff --git a/.venv/Lib/site-packages/httpcore/_backends/sync.py b/.venv/Lib/site-packages/httpcore/_backends/sync.py new file mode 100644 index 0000000..4018a09 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_backends/sync.py @@ -0,0 +1,241 @@ +from __future__ import annotations + +import functools +import socket +import ssl +import sys +import typing + +from .._exceptions import ( + ConnectError, + ConnectTimeout, + ExceptionMapping, + ReadError, + ReadTimeout, + WriteError, + WriteTimeout, + map_exceptions, +) +from .._utils import is_socket_readable +from .base import SOCKET_OPTION, NetworkBackend, NetworkStream + + +class TLSinTLSStream(NetworkStream): # pragma: no cover + """ + Because the standard `SSLContext.wrap_socket` method does + not work for `SSLSocket` objects, we need this class + to implement TLS stream using an underlying `SSLObject` + instance in order to support TLS on top of TLS. + """ + + # Defined in RFC 8449 + TLS_RECORD_SIZE = 16384 + + def __init__( + self, + sock: socket.socket, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ): + self._sock = sock + self._incoming = ssl.MemoryBIO() + self._outgoing = ssl.MemoryBIO() + + self.ssl_obj = ssl_context.wrap_bio( + incoming=self._incoming, + outgoing=self._outgoing, + server_hostname=server_hostname, + ) + + self._sock.settimeout(timeout) + self._perform_io(self.ssl_obj.do_handshake) + + def _perform_io( + self, + func: typing.Callable[..., typing.Any], + ) -> typing.Any: + ret = None + + while True: + errno = None + try: + ret = func() + except (ssl.SSLWantReadError, ssl.SSLWantWriteError) as e: + errno = e.errno + + self._sock.sendall(self._outgoing.read()) + + if errno == ssl.SSL_ERROR_WANT_READ: + buf = self._sock.recv(self.TLS_RECORD_SIZE) + + if buf: + self._incoming.write(buf) + else: + self._incoming.write_eof() + if errno is None: + return ret + + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError} + with map_exceptions(exc_map): + self._sock.settimeout(timeout) + return typing.cast( + bytes, self._perform_io(functools.partial(self.ssl_obj.read, max_bytes)) + ) + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError} + with map_exceptions(exc_map): + self._sock.settimeout(timeout) + while buffer: + nsent = self._perform_io(functools.partial(self.ssl_obj.write, buffer)) + buffer = buffer[nsent:] + + def close(self) -> None: + self._sock.close() + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + raise NotImplementedError() + + def get_extra_info(self, info: str) -> typing.Any: + if info == "ssl_object": + return self.ssl_obj + if info == "client_addr": + return self._sock.getsockname() + if info == "server_addr": + return self._sock.getpeername() + if info == "socket": + return self._sock + if info == "is_readable": + return is_socket_readable(self._sock) + return None + + +class SyncStream(NetworkStream): + def __init__(self, sock: socket.socket) -> None: + self._sock = sock + + def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + exc_map: ExceptionMapping = {socket.timeout: ReadTimeout, OSError: ReadError} + with map_exceptions(exc_map): + self._sock.settimeout(timeout) + return self._sock.recv(max_bytes) + + def write(self, buffer: bytes, timeout: float | None = None) -> None: + if not buffer: + return + + exc_map: ExceptionMapping = {socket.timeout: WriteTimeout, OSError: WriteError} + with map_exceptions(exc_map): + while buffer: + self._sock.settimeout(timeout) + n = self._sock.send(buffer) + buffer = buffer[n:] + + def close(self) -> None: + self._sock.close() + + def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> NetworkStream: + exc_map: ExceptionMapping = { + socket.timeout: ConnectTimeout, + OSError: ConnectError, + } + with map_exceptions(exc_map): + try: + if isinstance(self._sock, ssl.SSLSocket): # pragma: no cover + # If the underlying socket has already been upgraded + # to the TLS layer (i.e. is an instance of SSLSocket), + # we need some additional smarts to support TLS-in-TLS. + return TLSinTLSStream( + self._sock, ssl_context, server_hostname, timeout + ) + else: + self._sock.settimeout(timeout) + sock = ssl_context.wrap_socket( + self._sock, server_hostname=server_hostname + ) + except Exception as exc: # pragma: nocover + self.close() + raise exc + return SyncStream(sock) + + def get_extra_info(self, info: str) -> typing.Any: + if info == "ssl_object" and isinstance(self._sock, ssl.SSLSocket): + return self._sock._sslobj # type: ignore + if info == "client_addr": + return self._sock.getsockname() + if info == "server_addr": + return self._sock.getpeername() + if info == "socket": + return self._sock + if info == "is_readable": + return is_socket_readable(self._sock) + return None + + +class SyncBackend(NetworkBackend): + def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: + # Note that we automatically include `TCP_NODELAY` + # in addition to any other custom socket options. + if socket_options is None: + socket_options = [] # pragma: no cover + address = (host, port) + source_address = None if local_address is None else (local_address, 0) + exc_map: ExceptionMapping = { + socket.timeout: ConnectTimeout, + OSError: ConnectError, + } + + with map_exceptions(exc_map): + sock = socket.create_connection( + address, + timeout, + source_address=source_address, + ) + for option in socket_options: + sock.setsockopt(*option) # pragma: no cover + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + return SyncStream(sock) + + def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> NetworkStream: # pragma: nocover + if sys.platform == "win32": + raise RuntimeError( + "Attempted to connect to a UNIX socket on a Windows system." + ) + if socket_options is None: + socket_options = [] + + exc_map: ExceptionMapping = { + socket.timeout: ConnectTimeout, + OSError: ConnectError, + } + with map_exceptions(exc_map): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + for option in socket_options: + sock.setsockopt(*option) + sock.settimeout(timeout) + sock.connect(path) + return SyncStream(sock) diff --git a/.venv/Lib/site-packages/httpcore/_backends/trio.py b/.venv/Lib/site-packages/httpcore/_backends/trio.py new file mode 100644 index 0000000..6f53f5f --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_backends/trio.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +import ssl +import typing + +import trio + +from .._exceptions import ( + ConnectError, + ConnectTimeout, + ExceptionMapping, + ReadError, + ReadTimeout, + WriteError, + WriteTimeout, + map_exceptions, +) +from .base import SOCKET_OPTION, AsyncNetworkBackend, AsyncNetworkStream + + +class TrioStream(AsyncNetworkStream): + def __init__(self, stream: trio.abc.Stream) -> None: + self._stream = stream + + async def read(self, max_bytes: int, timeout: float | None = None) -> bytes: + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: ReadTimeout, + trio.BrokenResourceError: ReadError, + trio.ClosedResourceError: ReadError, + } + with map_exceptions(exc_map): + with trio.fail_after(timeout_or_inf): + data: bytes = await self._stream.receive_some(max_bytes=max_bytes) + return data + + async def write(self, buffer: bytes, timeout: float | None = None) -> None: + if not buffer: + return + + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: WriteTimeout, + trio.BrokenResourceError: WriteError, + trio.ClosedResourceError: WriteError, + } + with map_exceptions(exc_map): + with trio.fail_after(timeout_or_inf): + await self._stream.send_all(data=buffer) + + async def aclose(self) -> None: + await self._stream.aclose() + + async def start_tls( + self, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + timeout: float | None = None, + ) -> AsyncNetworkStream: + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: ConnectTimeout, + trio.BrokenResourceError: ConnectError, + } + ssl_stream = trio.SSLStream( + self._stream, + ssl_context=ssl_context, + server_hostname=server_hostname, + https_compatible=True, + server_side=False, + ) + with map_exceptions(exc_map): + try: + with trio.fail_after(timeout_or_inf): + await ssl_stream.do_handshake() + except Exception as exc: # pragma: nocover + await self.aclose() + raise exc + return TrioStream(ssl_stream) + + def get_extra_info(self, info: str) -> typing.Any: + if info == "ssl_object" and isinstance(self._stream, trio.SSLStream): + # Type checkers cannot see `_ssl_object` attribute because trio._ssl.SSLStream uses __getattr__/__setattr__. + # Tracked at https://github.com/python-trio/trio/issues/542 + return self._stream._ssl_object # type: ignore[attr-defined] + if info == "client_addr": + return self._get_socket_stream().socket.getsockname() + if info == "server_addr": + return self._get_socket_stream().socket.getpeername() + if info == "socket": + stream = self._stream + while isinstance(stream, trio.SSLStream): + stream = stream.transport_stream + assert isinstance(stream, trio.SocketStream) + return stream.socket + if info == "is_readable": + socket = self.get_extra_info("socket") + return socket.is_readable() + return None + + def _get_socket_stream(self) -> trio.SocketStream: + stream = self._stream + while isinstance(stream, trio.SSLStream): + stream = stream.transport_stream + assert isinstance(stream, trio.SocketStream) + return stream + + +class TrioBackend(AsyncNetworkBackend): + async def connect_tcp( + self, + host: str, + port: int, + timeout: float | None = None, + local_address: str | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: + # By default for TCP sockets, trio enables TCP_NODELAY. + # https://trio.readthedocs.io/en/stable/reference-io.html#trio.SocketStream + if socket_options is None: + socket_options = [] # pragma: no cover + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: ConnectTimeout, + trio.BrokenResourceError: ConnectError, + OSError: ConnectError, + } + with map_exceptions(exc_map): + with trio.fail_after(timeout_or_inf): + stream: trio.abc.Stream = await trio.open_tcp_stream( + host=host, port=port, local_address=local_address + ) + for option in socket_options: + stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover + return TrioStream(stream) + + async def connect_unix_socket( + self, + path: str, + timeout: float | None = None, + socket_options: typing.Iterable[SOCKET_OPTION] | None = None, + ) -> AsyncNetworkStream: # pragma: nocover + if socket_options is None: + socket_options = [] + timeout_or_inf = float("inf") if timeout is None else timeout + exc_map: ExceptionMapping = { + trio.TooSlowError: ConnectTimeout, + trio.BrokenResourceError: ConnectError, + OSError: ConnectError, + } + with map_exceptions(exc_map): + with trio.fail_after(timeout_or_inf): + stream: trio.abc.Stream = await trio.open_unix_socket(path) + for option in socket_options: + stream.setsockopt(*option) # type: ignore[attr-defined] # pragma: no cover + return TrioStream(stream) + + async def sleep(self, seconds: float) -> None: + await trio.sleep(seconds) # pragma: nocover diff --git a/.venv/Lib/site-packages/httpcore/_exceptions.py b/.venv/Lib/site-packages/httpcore/_exceptions.py new file mode 100644 index 0000000..bc28d44 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_exceptions.py @@ -0,0 +1,81 @@ +import contextlib +import typing + +ExceptionMapping = typing.Mapping[typing.Type[Exception], typing.Type[Exception]] + + +@contextlib.contextmanager +def map_exceptions(map: ExceptionMapping) -> typing.Iterator[None]: + try: + yield + except Exception as exc: # noqa: PIE786 + for from_exc, to_exc in map.items(): + if isinstance(exc, from_exc): + raise to_exc(exc) from exc + raise # pragma: nocover + + +class ConnectionNotAvailable(Exception): + pass + + +class ProxyError(Exception): + pass + + +class UnsupportedProtocol(Exception): + pass + + +class ProtocolError(Exception): + pass + + +class RemoteProtocolError(ProtocolError): + pass + + +class LocalProtocolError(ProtocolError): + pass + + +# Timeout errors + + +class TimeoutException(Exception): + pass + + +class PoolTimeout(TimeoutException): + pass + + +class ConnectTimeout(TimeoutException): + pass + + +class ReadTimeout(TimeoutException): + pass + + +class WriteTimeout(TimeoutException): + pass + + +# Network errors + + +class NetworkError(Exception): + pass + + +class ConnectError(NetworkError): + pass + + +class ReadError(NetworkError): + pass + + +class WriteError(NetworkError): + pass diff --git a/.venv/Lib/site-packages/httpcore/_models.py b/.venv/Lib/site-packages/httpcore/_models.py new file mode 100644 index 0000000..8a65f13 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_models.py @@ -0,0 +1,516 @@ +from __future__ import annotations + +import base64 +import ssl +import typing +import urllib.parse + +# Functions for typechecking... + + +ByteOrStr = typing.Union[bytes, str] +HeadersAsSequence = typing.Sequence[typing.Tuple[ByteOrStr, ByteOrStr]] +HeadersAsMapping = typing.Mapping[ByteOrStr, ByteOrStr] +HeaderTypes = typing.Union[HeadersAsSequence, HeadersAsMapping, None] + +Extensions = typing.MutableMapping[str, typing.Any] + + +def enforce_bytes(value: bytes | str, *, name: str) -> bytes: + """ + Any arguments that are ultimately represented as bytes can be specified + either as bytes or as strings. + + However we enforce that any string arguments must only contain characters in + the plain ASCII range. chr(0)...chr(127). If you need to use characters + outside that range then be precise, and use a byte-wise argument. + """ + if isinstance(value, str): + try: + return value.encode("ascii") + except UnicodeEncodeError: + raise TypeError(f"{name} strings may not include unicode characters.") + elif isinstance(value, bytes): + return value + + seen_type = type(value).__name__ + raise TypeError(f"{name} must be bytes or str, but got {seen_type}.") + + +def enforce_url(value: URL | bytes | str, *, name: str) -> URL: + """ + Type check for URL parameters. + """ + if isinstance(value, (bytes, str)): + return URL(value) + elif isinstance(value, URL): + return value + + seen_type = type(value).__name__ + raise TypeError(f"{name} must be a URL, bytes, or str, but got {seen_type}.") + + +def enforce_headers( + value: HeadersAsMapping | HeadersAsSequence | None = None, *, name: str +) -> list[tuple[bytes, bytes]]: + """ + Convienence function that ensure all items in request or response headers + are either bytes or strings in the plain ASCII range. + """ + if value is None: + return [] + elif isinstance(value, typing.Mapping): + return [ + ( + enforce_bytes(k, name="header name"), + enforce_bytes(v, name="header value"), + ) + for k, v in value.items() + ] + elif isinstance(value, typing.Sequence): + return [ + ( + enforce_bytes(k, name="header name"), + enforce_bytes(v, name="header value"), + ) + for k, v in value + ] + + seen_type = type(value).__name__ + raise TypeError( + f"{name} must be a mapping or sequence of two-tuples, but got {seen_type}." + ) + + +def enforce_stream( + value: bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes] | None, + *, + name: str, +) -> typing.Iterable[bytes] | typing.AsyncIterable[bytes]: + if value is None: + return ByteStream(b"") + elif isinstance(value, bytes): + return ByteStream(value) + return value + + +# * https://tools.ietf.org/html/rfc3986#section-3.2.3 +# * https://url.spec.whatwg.org/#url-miscellaneous +# * https://url.spec.whatwg.org/#scheme-state +DEFAULT_PORTS = { + b"ftp": 21, + b"http": 80, + b"https": 443, + b"ws": 80, + b"wss": 443, +} + + +def include_request_headers( + headers: list[tuple[bytes, bytes]], + *, + url: "URL", + content: None | bytes | typing.Iterable[bytes] | typing.AsyncIterable[bytes], +) -> list[tuple[bytes, bytes]]: + headers_set = set(k.lower() for k, v in headers) + + if b"host" not in headers_set: + default_port = DEFAULT_PORTS.get(url.scheme) + if url.port is None or url.port == default_port: + header_value = url.host + else: + header_value = b"%b:%d" % (url.host, url.port) + headers = [(b"Host", header_value)] + headers + + if ( + content is not None + and b"content-length" not in headers_set + and b"transfer-encoding" not in headers_set + ): + if isinstance(content, bytes): + content_length = str(len(content)).encode("ascii") + headers += [(b"Content-Length", content_length)] + else: + headers += [(b"Transfer-Encoding", b"chunked")] # pragma: nocover + + return headers + + +# Interfaces for byte streams... + + +class ByteStream: + """ + A container for non-streaming content, and that supports both sync and async + stream iteration. + """ + + def __init__(self, content: bytes) -> None: + self._content = content + + def __iter__(self) -> typing.Iterator[bytes]: + yield self._content + + async def __aiter__(self) -> typing.AsyncIterator[bytes]: + yield self._content + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{len(self._content)} bytes]>" + + +class Origin: + def __init__(self, scheme: bytes, host: bytes, port: int) -> None: + self.scheme = scheme + self.host = host + self.port = port + + def __eq__(self, other: typing.Any) -> bool: + return ( + isinstance(other, Origin) + and self.scheme == other.scheme + and self.host == other.host + and self.port == other.port + ) + + def __str__(self) -> str: + scheme = self.scheme.decode("ascii") + host = self.host.decode("ascii") + port = str(self.port) + return f"{scheme}://{host}:{port}" + + +class URL: + """ + Represents the URL against which an HTTP request may be made. + + The URL may either be specified as a plain string, for convienence: + + ```python + url = httpcore.URL("https://www.example.com/") + ``` + + Or be constructed with explicitily pre-parsed components: + + ```python + url = httpcore.URL(scheme=b'https', host=b'www.example.com', port=None, target=b'/') + ``` + + Using this second more explicit style allows integrations that are using + `httpcore` to pass through URLs that have already been parsed in order to use + libraries such as `rfc-3986` rather than relying on the stdlib. It also ensures + that URL parsing is treated identically at both the networking level and at any + higher layers of abstraction. + + The four components are important here, as they allow the URL to be precisely + specified in a pre-parsed format. They also allow certain types of request to + be created that could not otherwise be expressed. + + For example, an HTTP request to `http://www.example.com/` forwarded via a proxy + at `http://localhost:8080`... + + ```python + # Constructs an HTTP request with a complete URL as the target: + # GET https://www.example.com/ HTTP/1.1 + url = httpcore.URL( + scheme=b'http', + host=b'localhost', + port=8080, + target=b'https://www.example.com/' + ) + request = httpcore.Request( + method="GET", + url=url + ) + ``` + + Another example is constructing an `OPTIONS *` request... + + ```python + # Constructs an 'OPTIONS *' HTTP request: + # OPTIONS * HTTP/1.1 + url = httpcore.URL(scheme=b'https', host=b'www.example.com', target=b'*') + request = httpcore.Request(method="OPTIONS", url=url) + ``` + + This kind of request is not possible to formulate with a URL string, + because the `/` delimiter is always used to demark the target from the + host/port portion of the URL. + + For convenience, string-like arguments may be specified either as strings or + as bytes. However, once a request is being issue over-the-wire, the URL + components are always ultimately required to be a bytewise representation. + + In order to avoid any ambiguity over character encodings, when strings are used + as arguments, they must be strictly limited to the ASCII range `chr(0)`-`chr(127)`. + If you require a bytewise representation that is outside this range you must + handle the character encoding directly, and pass a bytes instance. + """ + + def __init__( + self, + url: bytes | str = "", + *, + scheme: bytes | str = b"", + host: bytes | str = b"", + port: int | None = None, + target: bytes | str = b"", + ) -> None: + """ + Parameters: + url: The complete URL as a string or bytes. + scheme: The URL scheme as a string or bytes. + Typically either `"http"` or `"https"`. + host: The URL host as a string or bytes. Such as `"www.example.com"`. + port: The port to connect to. Either an integer or `None`. + target: The target of the HTTP request. Such as `"/items?search=red"`. + """ + if url: + parsed = urllib.parse.urlparse(enforce_bytes(url, name="url")) + self.scheme = parsed.scheme + self.host = parsed.hostname or b"" + self.port = parsed.port + self.target = (parsed.path or b"/") + ( + b"?" + parsed.query if parsed.query else b"" + ) + else: + self.scheme = enforce_bytes(scheme, name="scheme") + self.host = enforce_bytes(host, name="host") + self.port = port + self.target = enforce_bytes(target, name="target") + + @property + def origin(self) -> Origin: + default_port = { + b"http": 80, + b"https": 443, + b"ws": 80, + b"wss": 443, + b"socks5": 1080, + b"socks5h": 1080, + }[self.scheme] + return Origin( + scheme=self.scheme, host=self.host, port=self.port or default_port + ) + + def __eq__(self, other: typing.Any) -> bool: + return ( + isinstance(other, URL) + and other.scheme == self.scheme + and other.host == self.host + and other.port == self.port + and other.target == self.target + ) + + def __bytes__(self) -> bytes: + if self.port is None: + return b"%b://%b%b" % (self.scheme, self.host, self.target) + return b"%b://%b:%d%b" % (self.scheme, self.host, self.port, self.target) + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(scheme={self.scheme!r}, " + f"host={self.host!r}, port={self.port!r}, target={self.target!r})" + ) + + +class Request: + """ + An HTTP request. + """ + + def __init__( + self, + method: bytes | str, + url: URL | bytes | str, + *, + headers: HeaderTypes = None, + content: bytes + | typing.Iterable[bytes] + | typing.AsyncIterable[bytes] + | None = None, + extensions: Extensions | None = None, + ) -> None: + """ + Parameters: + method: The HTTP request method, either as a string or bytes. + For example: `GET`. + url: The request URL, either as a `URL` instance, or as a string or bytes. + For example: `"https://www.example.com".` + headers: The HTTP request headers. + content: The content of the request body. + extensions: A dictionary of optional extra information included on + the request. Possible keys include `"timeout"`, and `"trace"`. + """ + self.method: bytes = enforce_bytes(method, name="method") + self.url: URL = enforce_url(url, name="url") + self.headers: list[tuple[bytes, bytes]] = enforce_headers( + headers, name="headers" + ) + self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = ( + enforce_stream(content, name="content") + ) + self.extensions = {} if extensions is None else extensions + + if "target" in self.extensions: + self.url = URL( + scheme=self.url.scheme, + host=self.url.host, + port=self.url.port, + target=self.extensions["target"], + ) + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.method!r}]>" + + +class Response: + """ + An HTTP response. + """ + + def __init__( + self, + status: int, + *, + headers: HeaderTypes = None, + content: bytes + | typing.Iterable[bytes] + | typing.AsyncIterable[bytes] + | None = None, + extensions: Extensions | None = None, + ) -> None: + """ + Parameters: + status: The HTTP status code of the response. For example `200`. + headers: The HTTP response headers. + content: The content of the response body. + extensions: A dictionary of optional extra information included on + the responseself.Possible keys include `"http_version"`, + `"reason_phrase"`, and `"network_stream"`. + """ + self.status: int = status + self.headers: list[tuple[bytes, bytes]] = enforce_headers( + headers, name="headers" + ) + self.stream: typing.Iterable[bytes] | typing.AsyncIterable[bytes] = ( + enforce_stream(content, name="content") + ) + self.extensions = {} if extensions is None else extensions + + self._stream_consumed = False + + @property + def content(self) -> bytes: + if not hasattr(self, "_content"): + if isinstance(self.stream, typing.Iterable): + raise RuntimeError( + "Attempted to access 'response.content' on a streaming response. " + "Call 'response.read()' first." + ) + else: + raise RuntimeError( + "Attempted to access 'response.content' on a streaming response. " + "Call 'await response.aread()' first." + ) + return self._content + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} [{self.status}]>" + + # Sync interface... + + def read(self) -> bytes: + if not isinstance(self.stream, typing.Iterable): # pragma: nocover + raise RuntimeError( + "Attempted to read an asynchronous response using 'response.read()'. " + "You should use 'await response.aread()' instead." + ) + if not hasattr(self, "_content"): + self._content = b"".join([part for part in self.iter_stream()]) + return self._content + + def iter_stream(self) -> typing.Iterator[bytes]: + if not isinstance(self.stream, typing.Iterable): # pragma: nocover + raise RuntimeError( + "Attempted to stream an asynchronous response using 'for ... in " + "response.iter_stream()'. " + "You should use 'async for ... in response.aiter_stream()' instead." + ) + if self._stream_consumed: + raise RuntimeError( + "Attempted to call 'for ... in response.iter_stream()' more than once." + ) + self._stream_consumed = True + for chunk in self.stream: + yield chunk + + def close(self) -> None: + if not isinstance(self.stream, typing.Iterable): # pragma: nocover + raise RuntimeError( + "Attempted to close an asynchronous response using 'response.close()'. " + "You should use 'await response.aclose()' instead." + ) + if hasattr(self.stream, "close"): + self.stream.close() + + # Async interface... + + async def aread(self) -> bytes: + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover + raise RuntimeError( + "Attempted to read an synchronous response using " + "'await response.aread()'. " + "You should use 'response.read()' instead." + ) + if not hasattr(self, "_content"): + self._content = b"".join([part async for part in self.aiter_stream()]) + return self._content + + async def aiter_stream(self) -> typing.AsyncIterator[bytes]: + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover + raise RuntimeError( + "Attempted to stream an synchronous response using 'async for ... in " + "response.aiter_stream()'. " + "You should use 'for ... in response.iter_stream()' instead." + ) + if self._stream_consumed: + raise RuntimeError( + "Attempted to call 'async for ... in response.aiter_stream()' " + "more than once." + ) + self._stream_consumed = True + async for chunk in self.stream: + yield chunk + + async def aclose(self) -> None: + if not isinstance(self.stream, typing.AsyncIterable): # pragma: nocover + raise RuntimeError( + "Attempted to close a synchronous response using " + "'await response.aclose()'. " + "You should use 'response.close()' instead." + ) + if hasattr(self.stream, "aclose"): + await self.stream.aclose() + + +class Proxy: + def __init__( + self, + url: URL | bytes | str, + auth: tuple[bytes | str, bytes | str] | None = None, + headers: HeadersAsMapping | HeadersAsSequence | None = None, + ssl_context: ssl.SSLContext | None = None, + ): + self.url = enforce_url(url, name="url") + self.headers = enforce_headers(headers, name="headers") + self.ssl_context = ssl_context + + if auth is not None: + username = enforce_bytes(auth[0], name="auth") + password = enforce_bytes(auth[1], name="auth") + userpass = username + b":" + password + authorization = b"Basic " + base64.b64encode(userpass) + self.auth: tuple[bytes, bytes] | None = (username, password) + self.headers = [(b"Proxy-Authorization", authorization)] + self.headers + else: + self.auth = None diff --git a/.venv/Lib/site-packages/httpcore/_ssl.py b/.venv/Lib/site-packages/httpcore/_ssl.py new file mode 100644 index 0000000..c99c5a6 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_ssl.py @@ -0,0 +1,9 @@ +import ssl + +import certifi + + +def default_ssl_context() -> ssl.SSLContext: + context = ssl.create_default_context() + context.load_verify_locations(certifi.where()) + return context diff --git a/.venv/Lib/site-packages/httpcore/_sync/__init__.py b/.venv/Lib/site-packages/httpcore/_sync/__init__.py new file mode 100644 index 0000000..b476d76 --- /dev/null +++ b/.venv/Lib/site-packages/httpcore/_sync/__init__.py @@ -0,0 +1,39 @@ +from .connection import HTTPConnection +from .connection_pool import ConnectionPool +from .http11 import HTTP11Connection +from .http_proxy import HTTPProxy +from .interfaces import ConnectionInterface + +try: + from .http2 import HTTP2Connection +except ImportError: # pragma: nocover + + class HTTP2Connection: # type: ignore + def __init__(self, *args, **kwargs) -> None: # type: ignore + raise RuntimeError( + "Attempted to use http2 support, but the `h2` package is not " + "installed. Use 'pip install httpcore[http2]'." + ) + + +try: + from .socks_proxy import SOCKSProxy +except ImportError: # pragma: nocover + + class SOCKSProxy: # type: ignore + def __init__(self, *args, **kwargs) -> None: # type: ignore + raise RuntimeError( + "Attempted to use SOCKS support, but the `socksio` package is not " + "installed. Use 'pip install httpcore[socks]'." + ) + + +__all__ = [ + "HTTPConnection", + "ConnectionPool", + "HTTPProxy", + "HTTP11Connection", + "HTTP2Connection", + "ConnectionInterface", + "SOCKSProxy", +] diff --git a/.venv/Lib/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..582126cf50147c25c8cbf92b950ea684b609925a GIT binary patch literal 1593 zcmbVM&2Jk;6rb5$d;Pg?YbZfbG>EE^Rq84^RtRCGlt!Qsk@nDK(Pq0lO}5@IX2waK z6QCTq=Zd%>t2a(ud*(kF;S(orR*eKI7v_z>qyPuHl7IWY_xZiw%+Jkc4XI}o-Z~su z2>lAm;wjCA=}$^HLlKH_f>P{a46>0Jsp*USC9ZoZqmj(81EVd+*-vR-R=v6mgz`Wu@)rd7>;AAZBA@WmmQ; z*mha|WT=$(%C(&d${qzFRpu7NY_5Fae02IX{|9LLic-%IMJ|qzYeZ&boLa6q#2t%O zlxru1P8W}0JH>cZ{8rxF-Zn3qd0yax&nQ912lv5qT5FY`RRCGy1u%_r-d5gk|Hi zK_=pqe#ltPMy9klO&&=rU3sisiKT~dqL{mro1om!rO}@O4K+X zt2J=%PQXQPcbgn>awiFR-@B{&qm10nALm~8h-OFLC-Fm%$AaFNx6VE22O7KS^I;a= z^!+%Ch3|KZp{y*FleKdz=`K7C80xDEzCyoTdTV0fiyz>rX||mSGMmm+IncImzZ?X~%)>507Gkhrb`by;s-dogsjc7?{cc!QXBGo) z#hcpRr&}NIX>$E<%3M(;UXn7|KPaR7_!r9XJnZv0e>vXhWW^f>Ghj^x3vSG21VcM% zuST$35m27B0rWs&S?o1{HG zYvJ-v&>6C|GWdnM7jy|(2G(tC=qj=XieJE`pli!oytoZGgX*%R4~_nutZ%1q67`vp zbv>1NE;K4_-&aS__s{)1S$S_pw)a^_g~6HU`wAu|#(0dbjnVZnx;jR0D`8?)u`{-= zO)l&A`t!?`y79IB=i~;ew4WmzJAYWEk|MW)NlB@HP1gRy^MVugk?+cz=!bI8MnATC Smif%YfOf0*y7_DkEBqTRf{yn9 literal 0 HcmV?d00001 diff --git a/.venv/Lib/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc b/.venv/Lib/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b30825a6e58497fa89dd7ebe848f3eec6730cee6 GIT binary patch literal 10389 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

+ FastAPI +